(By default) Game of the day - showcases the game card
-
(Swipe left) Story of the day - showcases the post
-
(Swipe left)Event of the day - showcases the community event
-
-
-
-
- Game of the day & Story of the day & Event of the day
-
-
-
-
Home - For you
-
-
Upcoming chart entry (would include all upcoming games already configured)
-
Game cards of upcoming games (Yesterday) showcases the first upcoming game of the previous day;Today showcases all the upcoming games of that day.Swipe left to see a total of 11 games in the Upcoming section marked by different dates
Game of the day + push notifications of the game page to all the players
-
-
*Newly released games or games in the testing phase will be given higher priority
-
-
-
-
Story of the day
-
-
*Posts like Dev logs or developer interviews would be given higher priority
-
-
-
-
Community Event & Event of the day
-
-
*Games with upcoming news such as major updates, giveaway, beta tests or official launch may be featured in the TapTap community events to encourage user-generated content.
*The game selected as the Game of the day will be put in the first place in the trending search on the same day
-
-
-
-
Upcoming
-
-
*Games with upcoming news such as official launch, open beta tests, closed beta tests, major updates will be configured in the upcoming section together with the exact date and event type
-
-
-
-
More exposure to game cards and posts
-
-
*Game cards and posts could be given more exposure in the feeds during important event milestones (official launch, beta tests, major update etc.)
-
-
-
-
Custom button in the game page
-
-
*The TapTap operation team can help customize the button text and redirect link according to the developers' needs (beta test recruitment, Discord invitation, official website pre-registration…)
-
-### Content types inside TapTap
-- Content produced by TapTap editors and professional content creators
- - Reviews and let’s plays produced by senior editors and content creators → Could produce targeted content such as previews and reviews to further promote the games
- - TapTap profile pages of editors and some content creators:
-
-
-
-- User generated content: voluntarily produced by TapTap users → Providing incentives such as hosting community events to encourage interations and promote the games
-- Content produced by developers: first-hand news & content → Posting regularly from the official account to interact with players is recommended
-
-## How to plan and create your official posts?
-
-### Recommended content scheduling
-
-
-
-
-
Stage
-
Content type
-
Examples
-
-
-
-
-
Onboarding
-
-
Dev say hi
-
Brief introduction of the game and the development team
-
Brief introduction of future roadmaps (for upcoming games)
-
Call to action: To direct players to follow the official account and pre-register for the game on TapTap
The post is genuine, down-to-earth, and has unique and distinctive content (such as the original intention behind making DoD, the challenges faced, and expressing gratitude to the players)
-
The post resonates with the players and the official team also actively responded and engaged with comments in the comment section
It's written more from an official perspective, and unlike the first two posts, it focuses mainly on the introduction of game mechanics and the future plans for the demo and full version release, since the game has not been launched yet
-
-
-
-
Warm-up
-
-
Devlog
-
For indie games, developers can talk about the motivation or source of inspiration, interesting behind-the-scenes stories, and character or gameplay designs. The general tone should be friendly and approachable
In the case of beta tests (OBT & CBT), it’s recommended that the developers could send out another annoucement after the beta test ends, and pin it to From the dev, in order to inform players of this information
-
-
-
-
-
-
-
-### Content production & posting guidelines
-
-- **Suggested format and length**:An article that contains a video (to increase conversion)/key visuals or screenshots + texts. The post should include the key information needed and the suggested length is between 250 - 350 words
-
-- **Things to check before sending out the post [!important]**:
- - *Whether a game has been “mentioned” in the post*:
- - There is a **Mentioned games** section on the right-hand sidebar of the post editor. At least one game needs to be added (usually the game to be promoted), otherwise, it will not be distributed in the home feeds and will not appear on the game page of relevant games. The game cards inserted in the post will be automatically synchronized here
- - *Whether the post status is **Public** *:
- - The post's status should be **Public** in the **Visibility** section at the bottom right. If it is **Unlisted or Private**, it will not be distributed normally in feeds (players will still be able to see the Unlisted post via the link or in the writer’s personal profile, but will not see it elsewhere in the app)
- - *Whether the post has a **thumbnail** *:
- - At least one image should be added to the post (if the post contains a video, then don’t forget to upload the video cover) to function as the post thumbnail
-- **Post title**:
- - The post title should include the game name, event type, and event time to help players quickly grasp the key message. Developers could also add a word or two on the game's most typical feature (Zombie shooter xxx; indie puzzle game xxx etc.) to the title
- - e.g.
-
- Fortnite new season update coming on Apr xx!
-
- Madtale | Closed beta test available now!
-
- Text-based puzzle game CaseCracker coming to TapTap on xxx!
-- **Call to action**:
- - Invite players to take the desired action (pre-register/join the official Discord server/participate in the beta test etc.) both at the beginning and the end of the post to emphasize its importance
-- **A Brief introduction of the game**:
- - Usually in one or two sentences about the general features and gameplay style of the game at the beginning of the post to help players know what the game is like
- - e.g.
-
- Life Makeover is a limitless dress-up and social simulation game where you can create your very own avatar, customize dress-up and makeup, design one-and-only garment by yourself, build your dream house and chill with your besties!
-
-- **Images & gifs**:
- - It’s recommended to add some key visuals, gifs or screenshots to the post to make it more accessible
-- **Insert game cards**:
- - It’s also recommended to insert the game card at the very beginning of the post for players to download/pre-register for the game directly, or to jump to the game description page for more details. Multiple games (no more than 20) can be added to the post and these selected games will also show up in the **Mentioned games** section
-
-
-
-
-
-- **Video**
-1. Upload the video directly (recommended). Please don’t forget to upload a video cover as well
-
-2. Insert a YouTube link of the video (not recommended). Please insert the link and choose [Display as card], so that players can directly watch the video on TapTap without jumping to external websites
-
-- **Discord invitation link**
-
- Please put the invitation link at the beginning of the post if you wanna specifically invite more players to join the official Discord server. Please choose [Display as card] for the link as well
-
-
-
-- **Hashtags**
-
- Relevant hashtags can be found and added in the **Tags** section on the right-hand sidebar (check this to see all the hashtags currently in use). Appropriate hashtags can help distribute the post to more players
-
-![](https://capacity-files.lcfile.com/3V6YHDRzLvhk865rgvpW0TQDa5kuzs45/image-20230419-033426.png)
-- **Schedule the post**
-
- Use the **Schedule** function to publish the post at the designated time
-
-![](https://capacity-files.lcfile.com/RDhj2erRMJkBRO3mgdWH0NrTRWzJNdvX/image-20230419-032447.png)
-
-### How to better promote the game to more players
-- **Plan the community posts properly**
- - Schedule the appropriate content according to the corresponding stages: [First reveal] - [Warm-up] - [Offically online], in order to accumulate pre-registrations and attention through constant content exposure
- - Please contact the TapTap BD team or operation team in advance in terms of important milestones to apply for promotional slots on TapTap
-
-- **Developers can use the following functions to better manage the game page**
-
- - *Add new moderators*:
-
- New moderators can be added using the **Admin tools** by the admin of this game. [Webpage version of the game page] - [**Admin tools** on the right-hand sidebar] - [Authority management]
-
-
- - *Pin important official posts to **From the dev** as featured posts*:
-
- [Webpage version of the game page] - [**Admin tools** on the right-hand sidebar] - [Posts] - [Use the post ID to search for the post] - [Pin the post]
-
- - *Push notifications in **Developer Center** (an effective approach to inform players interested in the game of the latest game news)*:
-
- - Developers can **push 1 post to all the players who have followed the game** (players who have pre-registered for/downloaded/wanted the game)
-
- - [Developer Center] - [Game Operations] - [Push Notification of Official Posts] - [New posts to push]
-
- - *The forum on the webpage version can also be managed via the **Admin tools**. Developers can pin revelant hashtags as filters and add useful links in the sidebar*:
-
- - **Pin hashtag**: [Webpage version of the game page] - [**Admin tools** on the right-hand sidebar] - [Manage pinned hashtags] - [Pin hashtag]
-
- - **Add useful links**: [Webpage version of the game page] - [**Admin tools** on the right-hand sidebar] - [Manage useful links] - [Add new links]
-
-
Hashtags and useful links in the game forum on the webpage version
-
-
-
-
-
-### Active interactions with players
-
-When responding to player reviews, it's important to be genuine and respectful. You should always make an effort to communicate with players in a calm and friendly manner, without being dismissive or indifferent. Honest and sincere communication can help players feel more secure.
-
-One thing to avoid is copying and pasting responses, as this can come across as impersonal and insincere. Instead, take the time to craft thoughtful and personalized responses that show you're engaged with the player's feedback.
-
-If you come across a long or negative review, it's important to respond in a timely manner and in a way that demonstrates your willingness to listen and address any issues raised. Responding to reviews can help build a positive image for your brand, and official responses will be given priority placement under player comments, making it more likely that subsequent players will see and engage with your message.
-
-
diff --git a/.ci/hk/en/sdk/TapPayments/_category_.json b/.ci/hk/en/sdk/TapPayments/_category_.json
deleted file mode 100644
index 9294586f6..000000000
--- a/.ci/hk/en/sdk/TapPayments/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "TapTap 支付",
- "collapsed": true,
- "position": 20
-}
diff --git a/.ci/hk/en/sdk/TapPayments/appendix/_category_.json b/.ci/hk/en/sdk/TapPayments/appendix/_category_.json
deleted file mode 100644
index e52e0c210..000000000
--- a/.ci/hk/en/sdk/TapPayments/appendix/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "附录",
- "collapsed": true,
- "position": 6
-}
diff --git a/.ci/hk/en/sdk/TapPayments/appendix/payment-methods.mdx b/.ci/hk/en/sdk/TapPayments/appendix/payment-methods.mdx
deleted file mode 100644
index 2ee7fb8c4..000000000
--- a/.ci/hk/en/sdk/TapPayments/appendix/payment-methods.mdx
+++ /dev/null
@@ -1,26 +0,0 @@
----
-title: 支持的支付方式
-sidebar_position: 1
----
-
-# 支持的支付方式
-
-TapTap Payments 支持 173 个国家或地区和 42 种货币,并正在不断集成更加本地化的支付方式。目前,我们在全球范围内支持以下支付方式:
-
-| **支付方式** | **支持的国家和地区** |
-| ------------------- | ------------------------ |
-| 信用卡和借记卡 | 173 个国家或地区 |
-| 支付宝 | 173 个国家或地区 |
-| PayPal | 173 个国家或地区 |
-| GrabPay | 菲律宾、新加坡、马拉西亚 |
-| DANA | 印度尼西亚 |
-| OVO | 印度尼西亚 |
-| QRIS | 印度尼西亚 |
-| GCash | 菲律宾 |
-| Maya | 菲律宾 |
-| PayNow | 新加坡 |
-| TrueMoney | 泰国 |
-| Rabbit LINE Pay | 泰国 |
-| Touch 'n Go eWallet | 马来西亚 |
-| Boost | 马来西亚 |
-| UPI | 印度 |
diff --git a/.ci/hk/en/sdk/TapPayments/appendix/regions-currencies.mdx b/.ci/hk/en/sdk/TapPayments/appendix/regions-currencies.mdx
deleted file mode 100644
index f28aec0bb..000000000
--- a/.ci/hk/en/sdk/TapPayments/appendix/regions-currencies.mdx
+++ /dev/null
@@ -1,186 +0,0 @@
----
-title: 支持的国家地区及货币
-sidebar_position: 2
----
-
-
-
-
-
-TapTap Payments 支持全球 173 个国家地区和 40 多种货币。在 本地货币不支持的国家地区,或在开发者没有设定价格的国家地区中,用户将以美元(USD)进行付款。另外,大部分货币为二位十进制货币,例如 USD 0.99,而另一部分则是零位十进制货币,例如 JPY 130。请在设置价格时参照以下标准:
-
-| **国家地区名称** | **国家地区代码** | **货币名称** | **货币精度** | **支付方式** |
-| ----------------------- | ---------------- | ------------ | ------------ | --------------------------------------------- |
-| Afghanistan | AF | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Albania | AL | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Algeria | DZ | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Angola | AO | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Anguilla | AI | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Antigua and Barbuda | AG | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Argentina | AR | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Armenia | AM | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Australia | AU | AUD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Austria | AT | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Azerbaijan | AZ | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Bahamas | BS | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Bahrain | BH | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Barbados | BB | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Belarus | BY | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Belgium | BE | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Belize | BZ | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Benin | BJ | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Bermuda | BM | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Bhutan | BT | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Bolivia | BO | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Bosnia and Herzegovina | BA | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Botswana | BW | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Brazil | BR | BRL | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Brunei Darussalam | BN | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Bulgaria | BG | BGN | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Burkina Faso | BF | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Cabo Verde | CV | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Cambodia | KH | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Cameroon | CM | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Canada | CA | CAD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Cayman Islands | KY | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Chad | TD | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Chile | CL | CLP | 整数 | 信用卡、借记卡、PayPal、支付宝 |
-| Colombia | CO | COP | 整数 | 信用卡、借记卡、PayPal、支付宝 |
-| Congo | CG | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Congo(DRC) | CD | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Costa Rica | CR | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Croatia | HR | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Cyprus | CY | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Czechia | CZ | CZK | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Denmark | DK | DKK | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Dominica | DM | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Dominican Republic | DO | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Ecuador | EC | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Egypt | EG | EGP | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| El Salvador | SV | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Estonia | EE | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Eswatini | SZ | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Fiji | FJ | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Finland | FI | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| France | FR | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Gabon | GA | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Gambia | GM | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Georgia | GE | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Germany | DE | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Ghana | GH | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Greece | GR | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Grenada | GD | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Guatemala | GT | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Guinea-Bissau | GW | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Guyana | GY | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Honduras | HN | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Hong Kong (China) | HK | HKD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Hungary | HU | HUF | 整数 | 信用卡、借记卡、PayPal、支付宝 |
-| Iceland | IS | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| India | IN | INR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝、UPI |
-| Indonesia | ID | IDR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝、DANA、OVO、QRIS |
-| Iraq | IQ | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Ireland | IE | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Israel | IL | ILS | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Italy | IT | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Ivory Coast | CI | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Jamaica | JM | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Japan | JP | JPY | 整数 | 信用卡、借记卡、PayPal、支付宝 |
-| Jordan | JO | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Kazakhstan | KZ | KZT | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Kenya | KE | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Kosovo | XK | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Kuwait | KW | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Kyrgyzstan | KG | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Laos | LA | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Latvia | LV | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Lebanon | LB | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Liberia | LR | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Libya | LY | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Lithuania | LT | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Luxembourg | LU | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Macau(China) | MO | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Madagascar | MG | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Malawi | MW | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Malaysia | MY | MYR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝、GrabPay、Touch 'n Go eWallet、Boost |
-| Maldives | MV | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Mali | ML | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Malta | MT | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Mauritania | MR | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Mauritius | MU | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Mexico | MX | MXN | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Micronesia (Federated States of) | FM | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Moldova, Republic of | MD | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Mongolia | MN | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Montenegro | ME | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Montserrat | MS | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Morocco | MA | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Mozambique | MZ | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Myanmar | MM | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Namibia | NA | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Nauru | NR | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Nepal | NP | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Netherlands | NL | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| New Zealand | NZ | NZD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Nicaragua | NI | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Niger | NE | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Nigeria | NG | NGN | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| North Macedonia | MK | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Norway | NO | NOK | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Oman | OM | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Pakistan | PK | PKR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Palau | PW | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Panama | PA | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Papua New Guinea | PG | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Paraguay | PY | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Peru | PE | PEN | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Philippines | PH | PHP | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝、GrabPay、GCash、Maya |
-| Poland | PL | PLN | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Portugal | PT | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Qatar | QA | QAR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Romania | RO | RON | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Rwanda | RW | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Saint Kitts and Nevis | KN | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Saint Lucia | LC | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Saint Vincent and the Grenadines | VC | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Sao Tome and Principe | ST | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Saudi Arabia | SA | SAR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Senegal | SN | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Serbia | RS | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Seychelles | SC | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Sierra Leone | SL | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Singapore | SG | SGD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝、GrabPay、PayNow |
-| Slovakia | SK | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Slovenia | SI | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Solomon Islands | SB | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| South Africa | ZA | ZAR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| South Korea | KR | KRW | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Spain | ES | EUR | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Sri Lanka | LK | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Suriname | SR | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Sweden | SE | SEK | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Switzerland | CH | CHF | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Taiwan (China) | TW | TWD | 整数 | 信用卡、借记卡、PayPal、支付宝 |
-| Tajikistan | TJ | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Tanzania | TZ | TZS | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Thailand | TH | THB | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝、TrueMoney、Rabbit LINE Pay |
-| Tonga | TO | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Trinidad and Tobago | TT | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Tunisia | TN | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Turkey | TR | TRY | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Turkmenistan | TM | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Turks and Caicos Islands | TC | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Uganda | UG | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Ukraine | UA | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| United Arab Emirates | AE | AED | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| United Kingdom | GB | GBP | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| United States | US | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Uruguay | UY | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Uzbekistan | UZ | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Vanuatu | VU | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Venezuela | VE | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Vietnam | VN | VND | 整数 | 信用卡、借记卡、PayPal、支付宝 |
-| Virgin Islands (British) | VG | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Yemen | YE | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Zambia | ZM | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
-| Zimbabwe | ZW | USD | 小数点后两位 | 信用卡、借记卡、PayPal、支付宝 |
diff --git a/.ci/hk/en/sdk/TapPayments/appendix/sandbox.mdx b/.ci/hk/en/sdk/TapPayments/appendix/sandbox.mdx
deleted file mode 100644
index f17e72db8..000000000
--- a/.ci/hk/en/sdk/TapPayments/appendix/sandbox.mdx
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: 沙盒环境
-sidebar_position: 1
----
-
-# 沙盒环境
-
-TapTap Payments 沙盒环境进行支付测试, 可以使用一些沙盒信用卡进行支付,
-
-## 沙盒账号设置
-进入开发者中心后台, 按照如下步骤设置和添加对应沙盒账号:
-
-![](https://img.tapimg.com/market/images/7309efbf7c1971d44c73a53494193bb4.png)
-
-## 沙盒信用卡
-在支付时, 可以使用以下沙盒信用卡进行支付测试:
-
-![](https://img.tapimg.com/market/images/7d3fb8a5a75d74f6f96f3c4a938693ff.png)
-
-## 沙盒异常模拟
-同时, 也可以使用以下卡号进行异常模拟
-
-![](https://img.tapimg.com/market/images/7b4394a2690b01a1008fcd362ec44a99.png)
diff --git a/.ci/hk/en/sdk/TapPayments/develop/android.mdx b/.ci/hk/en/sdk/TapPayments/develop/android.mdx
deleted file mode 100644
index 533331966..000000000
--- a/.ci/hk/en/sdk/TapPayments/develop/android.mdx
+++ /dev/null
@@ -1,220 +0,0 @@
----
-title: Android 集成指南
-sidebar_position: 2
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import { Conditional } from "/src/docComponents/conditional";
-import sdkVersions from '/src/docComponents/sdkVersions';
-import CodeBlock from '@theme/CodeBlock';
-
-
-## **环境要求**
-
-- Gradle 版本不低于 6.1.1,Android AGP 插件版本不低于 4.0.1;
-
-## **准备**
-
-- 参照 [准备工作](/sdk/start/get-ready/) 所述创建 app,配置 app 参数并且绑定 API 域名
-- 参照 [TapSDK 快速开始](/sdk/start/quickstart/) 配置包名和签名
-
-## **SDK 指南**
-
-### **SDK 集成**
-
-打开项目的 `project/app/build.gradle` 文件,添加 gradle 配置如下:
-
-
- {`dependencies {
- ...
- // TapTapIAP dependency
- implementation 'com.taptap.android.payment:iap:${sdkVersions.tapGlobalPayments.android}'
- implementation 'com.taptap.android.payment:base:${sdkVersions.tapGlobalPayments.android}'
- implementation 'com.taptap.android.payment:stripe:${sdkVersions.tapGlobalPayments.android}'
- implementation 'com.taptap.android.payment:braintree:${sdkVersions.tapGlobalPayments.android}'
- implementation 'com.taptap.android.payment:alipay:${sdkVersions.tapGlobalPayments.android}'
-}
-`}
-
-
-
-
-
-### **SDK 初始化**
-
-添加 TapTapIAP 的依赖项后,您需要初始化 `TapTapIAP` 实例。`TapTapIAP` 是 SDK 与应用的其余部分之间进行通信。`TapTapIAP` 为许多常见的操作提供了方法。
-
-首先 在应用启动时需要进行 SDK 初始化, 通过设置 `TapTapSdkOptions`, 完成 SDK 初始化, `TapTapSdk.init` 。
-
-这里需要设置对应在开发者后台申请的 `ClientID` 和 `ClientToken`,用于校验是否有权限使用 `TapTapIAP`。
-
-```java
-TapTapSdkOptions sdkOptions = new TapTapSdkOptions(
- "应用的 ClientID", // clientId 开发者平台申请
- "应用的 ClientToken", // clientToken 开发者平台申请
- TapTapRegion.GLOBAL, // 地区
- "", // 分包渠道名称
- "", // 游戏版本, 为空为null会取AppVersion
- false, // 是否自动上报GooglePlay内购支付成功事件 仅 [TapTapRegion.GLOBAL] 生效
- false, // 自定义字段是否能覆盖内置字段
- null, // 自定义属性,启动首个预置事件(device_login)会带上这些属性
- null, // OAID证书, 用于上报 OAID 仅 [TapTapRegion.CN] 生效
- false // 是否开启 log,建议 Debug 开启,Release 关闭
-);
-
-TapTapSdk.init(context, sdkOptions);
-```
-
-创建 `TapTapIAP`,请使用 `newBuilder()`这里会根据SDK.init所设置的 `ClientID` 和 `ClientToken`校验是否有权限使用 `TapTapIAP`。
-
-```java
-// 创建 TapTapIAP 实例
- TapTapIAP tapTapIAP = TapTapIAP.newBuilder().build();
- ```
-
-### **展示可供购买的商品**
-
-初始化完成 `TapTapIAP` 后,您就可以查询可售的商品并将其展示给用户了。
-
-查询应用内商品详情,请调用 `queryProductDetailsAsync()`。为了处理该异步操作的结果,您还必须指定实现 ` ProductDetailsResponseListener` 接口的监听器。然后,您可以替换 `onProductDetailsResponse()`,该方法会在查询完成时通知监听器,如以下示例所示:
-
-```java
-List queryProductList = new ArrayList<>();
-// 支持批量查询 Product, 设置好对应的 ProductID、ProductType
-// ProductType 目前仅支持 ProductType.INAPP
-for (int i = 0; i < products.length; i++) {
-
- String productId = products[i];
- Product product = Product.newBuilder()
- .setProductId(productId)
- .setProductType(ProductType.INAPP)
- .build();
- queryProductList.add(product);
-}
-QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder()
- .setProductList(queryProductList).build();
-tapTapIAP.queryProductDetailsAsync(params, new ProductDetailsResponseListener() {
- @Override
- public void onProductDetailsResponse(TapPaymentResult result,
- List productDetails, List unavailableProductIds) {
- ...
-
- // check TapPaymentResult
- // process returned productDetails
- }
-});
-```
-
-### **启动购买流程**
-
-如需从应用发起购买请求,请从应用的主线程调用 `launchBillingFlow()` 方法。此方法接受对 `BillingFlowParams` 对象的引用,该对象包含通过调用 `queryProductDetailsAsync()` 获取的相关 `ProductDetails` 对象。如需创建 `BillingFlowParams` 对象,请使用 `BillingFlowParams.Builder` 类。
-
-```java
-// An activity reference from which the billing flow will be launched.
-Activity activity = ...;
-ProductDetailsParams productDetailsParams =
- ProductDetailsParams.newBuilder()
- // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
- .setProductDetails(productDetails)
- .build();
-
-BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
- .setProductDetailsParams(productDetailsParams)
- .setObfuscatedAccountId("xxx") //Specifies an optional obfuscated string that is uniquely associated with the order(or another information) in your app.
- .build();
-// Launch the billing flow
-TapPaymentResult result = tapTapIAP.launchBillingFlow(activity,
- billingFlowParams,
- new PurchaseUpdatedListener() {
- @Override
- public void onPurchaseUpdated(TapPaymentResult result, Purchase purchases) {
- // To be implemented in a later section.
- }
- }
-);
-```
-
-`launchBillingFlow()` 方法会返回 `TapPaymentResponseCode` 中列出的几个响应代码之一。请务必检查此结果,以确保在启动购买流程时没有错误。`TapPaymentResponseCode` 为 `OK` 表示成功启动。成功调用 `launchBillingFlow()` 后,会向用户展示收银台。
-
-### **购买流程中订单状态监听**
-
-在购买流程中, `TapTapIAP` 会调用 `onPurchasesUpdated()`,以将购买的订单状态变更实时传给实现 PurchasesUpdatedListener 接口的监听器。您可以在初始化时使用 `setListener()` 方法指定监听器。您必须实现 `onPurchasesUpdated()` 来处理可能的响应代码。以下提供了一个 `onPurchasesUpdated()` 示例 :
-
-```java
-@Override
-public void onPurchaseUpdated(TapPaymentResult result, Purchase purchase) {
- if (result.getResponseCode() == TapPaymentResponseCode.OK
- && purchases != null) {
- handlePurchase(purchase);
- } else if (result.getResponseCode() == TapPaymentResponseCode.USER_CANCELED) {
- // Handle an error caused by a user cancelling the purchase flow.
- } else {
- // Handle any other error codes.
- }
-}
-```
-
-### **进行商品的发放,且完成订单**
-
-在用户进行完了任意商品的购买, 请确认为该用户发放对应的商品或者解锁对应的关卡。在确定商品发放之后需要调用 `finishPurchaseAsync` 告知 `TapTapIAP` 已完成商品的发放。以下是个代码实例:
-
-```java
-Purchase purchase = ...;
-FinishPurchaseParams params = FinishPurchaseParams.newBuilder()
- .setId(purchase.getOrderId()) // Required
- .setOrderToken(purchase.getOrderToken()) // Required
- .setPurchaseToken(purchase.getPurchaseToken()) // Required
- .build();
-tapTapIAP.finishPurchaseAsync(params, new FinishPurchaseResponseListener() {
- @Override
- public void onFinishPurchaseResponse(TapPaymentResult result, Purchase purchase) {
- }
-});
-```
-:::tip
-确认发放商品非常重要, 如果您没有调用 `finishPurchaseAsync` 来完成订单, 用户将无法再次购买该商品,且该订单将会在 3天后自动退款。
-:::
-
-### **获取未完成的订单列表**
-
-使用 `PurchasesUpdatedListener` 监听购买交易变更,无法完全确保您的应用会处理所有购买交易。有时您的应用可能不知道用户进行了部分购买交易。在下面这几种情况下,您的应用可能会跟踪不到或不知道购买交易的发生:
-
-- **在购买过程中出现网络问题**:用户成功购买了商品并收到了对应渠道的确认消息,但用户设备在通过 `PurchasesUpdatedListener` 收到购买交易的通知之前失去了网络连接。
-- **多部设备**:用户在一部设备上购买了一件商品,然后在切换设备时期望看到该商品。
-- **异常崩溃**:用户在外部购买成功时,应用出现了崩溃的情况。
-
-为了处理这些情况,请确保您的应用在 `onResume()` 方法中调用 `tapTapIAP.queryUnfinishedPurchaseAsync()`,以确保所有购买交易都得到正确处理。
-
-以下示例展示了如何提取用户的未完成订单列表:
-
-```java
-tapTapIAP.queryUnfinishedPurchaseAsync(new PurchasesResponseListener() {
- @Override
- public void onQueryPurchasesResponse(TapPaymentResult result, List purchases) {
- if (purchases != null) {
- // Process Purchases.
- ...
- ...
- }
- }
- });
-```
-
-### **处理 TapPaymentResult 响应代码**
-
-当使用 `TapTapIAP` 结算库调用触发操作时,该库会返回 `TapPaymentResult` 响应,并将结果告知开发者。例如,如果您使用 `queryProductDetailsAsync` 且返回 `OK` ,并提供正确的 `ProductDetails` 对象;或者返回了其他类型,代表了无法提供 `ProductDetails` 对象的原因。
-
-并非所有类型都是错误。下面列举了一些 `TapPaymentResponseCode` 不是错误的:
-
-- `TapPaymentResponseCode.OK`:代表业务已成功执行。
-- `TapPaymentResponseCode.USER_CANCELED`:代表用户没有完成流程,就离开了页面
-
-其他的一些错误类型可以用于调试和上报使用:
-
-| **可重试的 CODE** | **问题** | **可以尝试的解决方案** |
-| ------------------ | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
-| NETWORK_ERROR | 此错误代表设备和 TapTapIAP 系统之间的网络连接出现问题 | 可以使用简单的重试策略或指数退避算法 |
-| ITEM_ALREADY_OWNED | 这个类型表明用户已经购买过 非消耗品 商品, 再次购买时会返回该错误 | 为了避免出现这个问题, 在展示商品界面就提示用户已经购买该商品, 且无法点进进行购买流程. |
-| USER_CANCELED | 用户已退出结算流程 | |
-| ITEM_UNAVAILABLE | 商品无效, 有可能是商品已经过期或者商品已经被下架 | 确保您通过 `queryProductDetailsAsync` 刷新商品详情。如果商品无效则不在界面上展示给用户 |
-| DEVELOPER_ERROR | 这是一个严重错误,表明您未正确使用 API。例如,向 launchBillingFlow 提供不正确的参数可能会导致此错误 | |
diff --git a/.ci/hk/en/sdk/TapPayments/develop/faq.mdx b/.ci/hk/en/sdk/TapPayments/develop/faq.mdx
deleted file mode 100644
index 9a2302a28..000000000
--- a/.ci/hk/en/sdk/TapPayments/develop/faq.mdx
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: 常见问题
-sidebar_position: 4
----
-
-### 1、Unity 2020.3.15 之前的版本升级 Gradle 版本的操作步骤
-
-在 `Player Settings` -> `Player` -> `Android Tab` -> `Publish Settings` -> `Build`,然后勾选 **Custom Base Gradle Template**、**Custom Launcher Gradle Template**、**Custom Main Gradle Template**。
-
-![](https://capacity-files.lcfile.com/5nSUEX6IWVRkwu4Nep1pXo6vUnl9VppH/%E5%8D%87%E7%BA%A7gradle_1.png)
-
-以下更改应用于 `Assets/Plugins/Android/` 该文件夹下生成的三个文件 `mainTemplate.gradle`、`launcherTemplate.gradle`、`baseProjectTemplate.gradle`。
-
-打开 `baseProjectTemplate.gradle` 修改文件内容:
-
-```groovy
-dependencies {
- ...
- // 将 3.x.0 版本修改为 4.0.0
- //classpath 'com.android.tools.build:gradle:3.x.0'
- classpath 'com.android.tools.build:gradle:4.0.0'
-}
-```
-
-分别打开 `mainTemplate.gradle` 和 `launcherTemplate.gradle` 文件,找到 `lintOptions` 标签, 分别添加 **checkReleaseBuilds false** 配置:
-
-```groovy
-lintOptions {
- abortOnError false
- checkReleaseBuilds false
-}
-```
-
-
-同时,为了将 [Gradle 版本和 Android Gradle Plugin 版本对应](https://developer.android.com/studio/releases/gradle-plugin#expandable-1),需要更新 Gradle 版本,下载 [6.5.0 版本的 Gradle](https://services.gradle.org/distributions/gradle-6.5-all.zip),解压后放到自定义的文件夹中,同时**不**勾选 Unity 中的 `Preferences` -> `External Tools`-> `Android` -> `Gradle Installed with Unity(recommend)`,改为选择解压后 Gradle 文件夹的位置,如 `/gradle-6.5.0`。
-
-![](https://capacity-files.lcfile.com/hrkFCRy9VuLapvsanFm6nhpkHEEz0qVE/%E5%8D%87%E7%BA%A7gradle_2.png)
\ No newline at end of file
diff --git a/.ci/hk/en/sdk/TapPayments/develop/server.mdx b/.ci/hk/en/sdk/TapPayments/develop/server.mdx
deleted file mode 100644
index efbe5b4b9..000000000
--- a/.ci/hk/en/sdk/TapPayments/develop/server.mdx
+++ /dev/null
@@ -1,687 +0,0 @@
----
-title: 服务端开发指南
-sidebar_position: 3
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-
-# 服务端开发指南
-
-:::tip
-服务通用规则请参考 [请求规则和签算](/sdk/TapPayments/develop/server/#请求规则和签算)
-:::
-
-## 主查服务
-
-#### 请求域名:
-- `https://cloud-payment.tapapis.com`
-
-
-| ** 接口地址 ** | **Method** | **描述** |
-| ----------------------------- | ---------- | -------------------- |
-| `/order/v1/info?client_id={{client_id}}&order_id={{order_id}}` | `GET` | 查询订单信息 |
-| `/order/v1/unconfirmed?client_id={{client_id}}` | `GET` | 查询未核销的订单列表 |
-| `/order/v1/verify?client_id={{client_id}}` | `POST` | 核销订单 |
-
-### 查询订单信息
-
-#### 服务 URL
-- https://{{domain}}/order/v1/info?client_id={{client_id}}&order_id={{order_id}}
-
-#### 请求方式
-- GET
-
-#### 请求签算
-- 详见 [请求规则和签算](/sdk/TapPayments/develop/server/#请求规则和签算)
-
-通过订单 ID 查询订单详细信息和支付状态
-
-```
-curl -X GET \
- -H 'X-Tap-Sign: {{signature}}' \
- -H 'X-Tap-Ts: {{unix timestamp}}' \
- -H 'X-Tap-Nonce: {{random nonce}}' \
- https://{{domain}}/order/v1/info?client_id={{client_id}}&order_id={{order_id}}
-```
-
-`data.order` 对象结构见 [订单信息](/sdk/TapPayments/develop/server/#订单信息)
-
-```
-{
- "data": {
- "order": {}
- },
- "success": true
-}
-```
-
-### 查询未核销的订单列表
-
-#### 服务 URL
-- https://{{domain}}/order/v1/unconfirmed?client_id={{client_id}}
-
-#### 请求方式
-- GET
-
-#### 请求签算
-- 详见 [请求规则和签算](/sdk/TapPayments/develop/server/#请求规则和签算)
-
-查询当前未核销的订单列表,正常情况下在用户支付成功后,应通过 verify 接口核销订单并同时保证对用户发货成功。如果因异常原因没有完成核销,可以通过此接口查询,重新 verify 并完成发货。
-
-```
-curl -X GET \
- -H 'X-Tap-Sign: {{signature}}' \
- -H 'X-Tap-Ts: {{unix timestamp}}' \
- -H 'X-Tap-Nonce: {{random nonce}}' \
- https://{{domain}}/order/v1/unconfirmed?client_id={{client_id}}
-```
-
-`data.list` 数组内对象结构见 [订单信息](/sdk/TapPayments/develop/server/#订单信息)
-
-```
-{
- "data": {
- "list": [
- {}
- ]
- },
- "success": true
-}
-```
-
-### 核销订单
-
-#### 服务 URL
-- https://{{domain}}/order/v1/verify?client_id={{client_id}}
-
-#### 请求方式
-- POST[application/json; charset=utf-8]
-
-#### 请求正文信息
-
-| ** 参数名称 ** | ** 必填 ** | ** 格式 ** | ** 描述 ** |
-| --------------| --------------| --------------| --------------|
-| `order_id` | Y | string | 订单唯一 ID |
-| `purchase_token` | Y | string | 用于订单核销的 token |
-
-#### 请求签算
-- 详见 [请求规则和签算](/sdk/TapPayments/develop/server/#请求规则和签算)
-
-当支付成功后,核销订单表示已经确认支付结果并已对买家完成发货,订单状态也会从 `charge.succeeded` 变为 `charge.confirmed`
-
-```
-curl -X POST \
- -H 'X-Tap-Sign: {{signature}}' \
- -H 'X-Tap-Ts: {{unix timestamp}}' \
- -H 'X-Tap-Nonce: {{random nonce}}' \
- -H 'Content-Type: application/json; charset=utf-8'
- -d '{"order_id":"{{order_id}}","purchase_token":"{{purchase_token}}"}'
- https://{{domain}}/order/v1/verify?client_id={{client_id}}
-```
-
-`data.order` 对象结构见 [订单信息](/sdk/TapPayments/develop/server/#订单信息)
-
-```
-{
- "data": {
- "order": {}
- },
- "success": true
-}
-```
-
-## Webhook 回调
-
-:::tip
-同样的通知可能会多次发送,商户系统必须正确处理重复通知。
-
-推荐做法是:当商户系统收到通知时,首先进行签名验证,然后检查对应业务数据的状态,如未处理,则进行处理;如已处理,则直接返回成功。
-
-在处理业务数据时建议采用数据锁进行并发控制,以避免可能出现的数据异常。
-:::
-
-### Webhook 说明
-
-目前 Webhook 支持监听「充值成功」「退款成功」「退款失败」事件。对于「充值成功」建议采取主动核销订单,根据订单状态完成发货。
-
-1. 需要依次进入 **TapTap 开发者中心 > 你的游戏 > 游戏服务 > TapPayment > 商品与订单 > API 密钥**,检查是否有已生效的密钥,没有则需要 **添加新的密钥**
-2. 需要依次进入 **TapTap 开发者中心 > 你的游戏 > 游戏服务 > TapPayment > 商品与订单 > Webhooks 设置 > 添加**,添加有效的 **充值成功** URL。
-
-### Webhook 请求
-
-#### 服务 URL
-- 由开发者提供,在 Webhooks 设置中添加
-
-#### 请求方式
-- POST[application/json; charset=utf-8]
-
-#### 请求正文信息
-
-| ** 参数名称 ** | ** 必填 ** | ** 格式 ** | ** 描述 ** |
-| --------------| --------------| --------------| --------------|
-| `order` | Y | object | 对象结构见 [订单信息](/sdk/TapPayments/develop/server/#订单信息) |
-| `event_type` | Y | string | 事件枚举见 [Webhook的事件枚举](/sdk/TapPayments/develop/server/#webhook的事件枚举) |
-
-#### 请求签算
-- 详见 [请求规则和签算](/sdk/TapPayments/develop/server/#请求规则和签算)
-
-```
-curl -X POST \
- -H 'X-Tap-Sign: {{signature}}' \
- -H 'X-Tap-Ts: {{unix timestamp}}' \
- -H 'X-Tap-Nonce: {{random nonce}}' \
- -H 'Content-Type: application/json; charset=utf-8'
- -d '{"order":{},"event_type":"charge.succeeded"}'
- {{your webhook url}}
-
-```
-
-### Webhook 响应
-
-```
-{
- "code": "SUCCESS",
- "msg": ""
-}
-```
-
-#### 字段描述
-
-| **字段** | **类型** | **是否必须** | **描述** |
-| -------- | -------- | ------------ | ---------------------------------------- |
-| code | string | Y | 状态码,`SUCCESS` 为接收成功,`FAIL` 或其他均认为失败 |
-| msg | string | N | 当接收失败时需返回失败原因 |
-
-## 请求规则和签算
-
-### 请求 Headers
-
-| **Header** | **是否必须** | **描述** |
-| ----------- | ------------ | ------------------------------------------------------------ |
-| `X-Tap-Sign` | Y | 接口签算,详见 [签算](/sdk/TapPayments/develop/server/#签算) |
-| `X-Tap-Ts` | Y | 请求方当前时间 unix timestamp |
-| `X-Tap-Nonce` | Y | 随机数,需要大于等于 6 字节,小于等于 60 字节,每次请求需重新生成 |
-
-### 请求 Request
-
-#### 保留参数
-
-所有 HTTP METHOD 必传,需要作为查询参数的一部分
-
-| **Key** | **描述** |
-| --------- | --------------- |
-| client_id | 开发平台应用 ID |
-
-```
-https://{{domain}}/order/v1/info?client_id={{client_id}}&order_id={{order_id}}
-```
-
-#### 当请求方法是 POST
-
-HTTP body 需使用 JSON 编码格式传输参数,即请求 headers 中应该携带 `Content-Type: application/json; charset=utf-8`
-
-```
-curl -X POST \
- -H 'X-Tap-Sign: {{signature}}' \
- -H 'X-Tap-Ts: {{unix timestamp}}' \
- -H 'X-Tap-Nonce: {{random nonce}}' \
- -H 'Content-Type: application/json; charset=utf-8'
- -d '{"order_id":{{order_id}},"purchase_token":"{{purchase_token}}"}'
- https://{{domain}}/order/v1/verify?client_id={{client_id}}
-```
-
-### 请求 Response
-
-#### 请求成功响应
-
-```
-{
- "data": {},
- "now": 1640966400,
- "success": true
-}
-```
-
-#### 请求异常响应
-
-```
-{
- "data": {
- "code": 100004,
- "msg": "NotFound: Unknown Error",
- "error_description": "order not found"
- },
- "now": 1640966400,
- "success": false
-}
-```
-
-#### 字段描述
-
-| **字段** | **类型** | **是否必须** | **描述** |
-| -------- | -------- | ------------ | --------------------------- |
-| data | object | Y | 业务数据,或异常信息描述 |
-| now | int | Y | 服务端时间 (unix timestamp) |
-| success | bool | Y | 响应状态,`true` 为成功 |
-
-异常响应 `data` 字段描述
-
-| **字段** | **类型** | **是否必须** | **描述** |
-| ----------------- | -------- | ------------ |------------------------------------------------------------|
-| code | int | Y | [错误码](/sdk/TapPayments/develop/server/#错误码) |
-| msg | string | Y | 通用错误描述 |
-| error_description | string | Y | 详细错误描述,辅助理解和解决发生的错误 |
-
-
-### 签算
-
-签算采用 HMAC-SHA256 算法
-
-#### 密钥获取
-- 可在 **TapTap 开发者中心 > 你的游戏 > 游戏服务 > TapPayment > 商品与订单 > API 密钥** 查看。
-
-#### 签算说明
-
-sign = HMAC.New(Sha256, "{{Server Secret}}").Hash(message)
-
-以下为 `message` 组成
-
-```
-{method}\n
-{url_path_and_query}\n
-{headers}\n
-{body}\n
-```
-
-**method:** 请求 HTTP 方法,如 GET、POST。
-
-**url_path_and_query:** 完整请求路径及参数,如 /service/v1/method?client_id={{client_id}}&foo={{foo}}&bar={{bar}}&foo_bar={{foo_bar}}
-
-**headers:** 所有 `X-Tap-` 前缀的 headers 组合,将 keys 按 ASCII-code order 排序后以换行符 \n 为分隔符拼接。如 {key1}:{value1}\n{key2}:{value2}\n{key3}:{value3}。**为避免不同网络框架导致 keys 的排序结果不一致,签算时需要执行 key 转小写操作 key = tolower(key)**
-
-**body:** 请求体,如果请求体为空,则最后一行仅为一个 \n
-
-以下为请求体为空时的 `message` 组成
-
-```
-{method}\n
-{url_path_and_query}\n
-{headers}\n
-\n
-```
-
-:::tip
-请求体 body,无需处理请求参数顺序。建议用 String 接收 Webhook 请求的 RequestBody,验证请求签算后,再完成数据的反序列化
-:::
-
-
-
-<>
-
-```java
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import java.net.URI;
-import java.net.http.HttpRequest;
-import java.net.http.HttpClient;
-import java.net.http.HttpResponse;
-import java.util.*;
-import java.util.stream.Collectors;
-public class SignatureExample {
-
- public static String signRequest(String method, URI uri, String body, Map> headers, String secret) throws Exception {
- String urlPathAndQueryPart = uri.getRawPath() + (uri.getRawQuery() != null ? "?" + uri.getRawQuery() : "");
- String headersPart = getHeadersPart(headers);
-
- // 包含请求体的签名字符串部分
- String signParts = method.toUpperCase() + "\n" + urlPathAndQueryPart + "\n" + headersPart + "\n" + body + "\n";
-
- System.out.println("Sign Parts:\n" + signParts);
-
- Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
- SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
- sha256_HMAC.init(secretKey);
-
- byte[] hash = sha256_HMAC.doFinal(signParts.getBytes());
- return Base64.getEncoder().encodeToString(hash);
- }
-
- private static String getHeadersPart(Map> headers) throws Exception {
- TreeMap sortedHeaders = new TreeMap<>();
- headers.forEach((key, value) -> {
- String lowerKey = key.toLowerCase();
- if (lowerKey.equals("x-tap-sign")) {
- return;
- }
- if (lowerKey.startsWith("x-tap-")) {
- if (value.size() > 1) {
- throw new RuntimeException("Invalid header, " + lowerKey + " has multiple values");
- }
- sortedHeaders.put(lowerKey, value.get(0));
- }
- });
-
- return sortedHeaders.entrySet().stream()
- .map(entry -> entry.getKey() + ":" + entry.getValue())
- .collect(Collectors.joining("\n"));
- }
-
- public static void main(String[] args) {
- try {
- String secret = "VRy8aS2xbwImQUwtxc6vs4v51DaJWdlO";
- String body = "{\"event_type\":\"charge.succeeded\",\"order\":{\"order_id\":\"1790288650833465345\",\"purchase_token\":\"rT2Et9p0cfzq4fwjrTsGSacq0jQExFDqf5gTy1alp+Y=\",\"client_id\":\"o6nD4iNavjQj75zPQk\",\"open_id\":\"4+Axcl2RFgXbt6MZwdh++w==\",\"user_region\":\"US\",\"goods_open_id\":\"com.goods.open_id\",\"goods_name\":\"TestGoodsName\",\"status\":\"charge.succeeded\",\"amount\":\"19000000000\",\"currency\":\"USD\",\"create_time\":\"1716168000\",\"pay_time\":\"1716168000\",\"extra\":\"1111111111111111111\"}}";
- URI uri = new URI("https://example.com/my-service/v1/my-method");
-
- HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
- .uri(uri)
- .header("Content-Type", "application/json; charset=utf-8")
- .header("X-Tap-Ts", "1716168000")
- .header("X-Tap-Nonce", "V7v7zJ");
-
- // Considering body to be added for a POST request
- HttpRequest request = requestBuilder
- .POST(HttpRequest.BodyPublishers.ofString(body))
- .build();
-
- String method = "POST"; // Since we are using the POST method in this example
- String signature = signRequest(method, uri, body, request.headers().map(), secret);
- System.out.println("Signature: " + signature);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-}
-```
-
->
-<>
-
-```go
-package main
-
-import (
- "bytes"
- "context"
- "crypto/hmac"
- "crypto/sha256"
- "encoding/base64"
- "fmt"
- "io"
- "net/http"
- "sort"
- "strings"
-)
-
-func main() {
- //nolint:gosec
- secret := "VRy8aS2xbwImQUwtxc6vs4v51DaJWdlO"
- body := []byte(`{"event_type":"charge.succeeded","order":{"order_id":"1790288650833465345","purchase_token":"rT2Et9p0cfzq4fwjrTsGSacq0jQExFDqf5gTy1alp+Y=","client_id":"o6nD4iNavjQj75zPQk","open_id":"4+Axcl2RFgXbt6MZwdh++w==","user_region":"US","goods_open_id":"com.goods.open_id","goods_name":"TestGoodsName","status":"charge.succeeded","amount":"19000000000","currency":"USD","create_time":"1716168000","pay_time":"1716168000","extra":"1111111111111111111"}}`)
- url := "https://example.com/my-service/v1/my-method"
- method := "POST"
- header := http.Header{
- "Content-Type": {"Content-Type: application/json; charset=utf-8"},
- "X-Tap-Ts": {"1716168000"},
- "X-Tap-Nonce": {"V7v7zJ"},
- }
- ctx := context.Background()
- req, _ := http.NewRequestWithContext(ctx, method, url, bytes.NewBuffer(body))
- req.Header = header
- sign, err := Sign(req, secret)
- if err != nil {
- panic(err)
- }
- req.Header.Set("X-Tap-Sign", sign)
- fmt.Println(sign)
-}
-
-// Sign signs the request.
-func Sign(req *http.Request, secret string) (string, error) {
- methodPart := req.Method
- urlPathAndQueryPart := req.URL.RequestURI()
- headersPart, err := getHeadersPart(req.Header)
- if err != nil {
- return "", err
- }
- bodyPart, err := io.ReadAll(req.Body)
- if err != nil {
- return "", err
- }
- signParts := methodPart + "\n" + urlPathAndQueryPart + "\n" + headersPart + "\n" + string(bodyPart) + "\n"
- fmt.Println(signParts)
- h := hmac.New(sha256.New, []byte(secret))
- h.Write([]byte(signParts))
- rawSign := h.Sum(nil)
- sign := base64.StdEncoding.EncodeToString(rawSign)
- return sign, nil
-}
-
-// getHeadersPart returns the headers part of the request.
-func getHeadersPart(header http.Header) (string, error) {
- var headerKeys []string
- for k, v := range header {
- k = strings.ToLower(k)
- if !strings.HasPrefix(k, "x-tap-") {
- continue
- }
- if k == "x-tap-sign" {
- continue
- }
- if len(v) > 1 {
- return "", fmt.Errorf("invalid header, %q has multiple values", k)
- }
- headerKeys = append(headerKeys, k)
- }
- sort.Strings(headerKeys)
- headers := make([]string, 0, len(headerKeys))
- for _, k := range headerKeys {
- headers = append(headers, fmt.Sprintf("%s:%s", k, header.Get(k)))
- }
- return strings.Join(headers, "\n"), nil
-}
-```
-
->
-
-<>
-
-```python
-import base64
-import hashlib
-import hmac
-from typing import Dict, List
-from urllib.parse import urlparse
-
-def sign_request(method: str, url: str, body: str, headers: Dict[str, List[str]], secret: str) -> str:
- # 提取URL路径和查询字符串部分
- parsed_url = urlparse(url)
- url_path_and_query = parsed_url.path + ('?' + parsed_url.query if parsed_url.query else '')
-
- # 获取符合条件的头部信息并排序
- headers_part = get_headers_part(headers)
-
- # 拼接签名字符串
- sign_parts = f"{method}\n{url_path_and_query}\n{headers_part}\n{body}\n"
-
- print("Sign Parts:\n", sign_parts)
-
- # 使用HMAC SHA256算法生成签名
- raw_sign = hmac.new(secret.encode(), sign_parts.encode(), hashlib.sha256).digest()
-
- # 对签名进行Base64编码
- sign = base64.b64encode(raw_sign).decode()
-
- return sign
-
-def get_headers_part(headers: Dict[str, List[str]]) -> str:
- # 筛选和排序头部
- headers = {k.lower(): v for k, v in headers.items() if k.lower().startswith('x-tap-') and k.lower() != "x-tap-sign"}
- header_keys = sorted(headers.keys())
-
- # 组装头部字符串
- headers_str = '\n'.join(f"{k}:{headers[k][0]}" for k in header_keys if len(headers[k]) == 1)
-
- if any(len(headers[k]) > 1 for k in header_keys):
- raise ValueError("Invalid header: has multiple values")
-
- return headers_str
-
-# 示例使用
-secret = "VRy8aS2xbwImQUwtxc6vs4v51DaJWdlO"
-url = "https://example.com/my-service/v1/my-method"
-method = "POST"
-body = '{"event_type":"charge.succeeded","order":{"order_id":"1790288650833465345",' \
- '"purchase_token":"rT2Et9p0cfzq4fwjrTsGSacq0jQExFDqf5gTy1alp+Y=","client_id":"o6nD4iNavjQj75zPQk",' \
- '"open_id":"4+Axcl2RFgXbt6MZwdh++w==","user_region":"US","goods_open_id":"com.goods.open_id",' \
- '"goods_name":"TestGoodsName","status":"charge.succeeded","amount":"19000000000","currency":"USD",' \
- '"create_time":"1716168000","pay_time":"1716168000","extra":"1111111111111111111"}}'
-headers = {
- "Content-Type": ["Content-Type: application/json; charset=utf-8"],
- "X-Tap-Ts": ["1716168000"],
- "X-Tap-Nonce": ["V7v7zJ"],
-}
-
-try:
- sign = sign_request(method, url, body, headers, secret)
- print("Signature: ", sign)
-except Exception as e:
- print("Error: ", str(e))
-
-```
-
->
-
-<>
-
-```php
- $value) {
- $key = strtolower($key);
- if (count($value) > 1) {
- throw new Exception("Multiple values for header: " . $key);
- }
- if ($key === "x-tap-sign") {
- continue;
- }
- if (strpos($key, 'x-tap-') === 0) {
- $signHeaders[$key] = $value; // Assuming each header has a single value
- }
- }
- $headerKeys = [];
- foreach ($signHeaders as $key => $value) {
- if (!(strpos($key, 'x-tap-') === 0)) {
- continue;
- }
- $headerKeys[] = $key;
- }
- sort($headerKeys);
- $headerParts = [];
- foreach ($headerKeys as $key) {
- $headerParts[] = $key . ':' . $signHeaders[$key][0];
- }
- return implode("\n", $headerParts);
-}
-
-// 示例使用
-$secret = "VRy8aS2xbwImQUwtxc6vs4v51DaJWdlO";
-$url = "https://example.com/my-service/v1/my-method";
-$method = "POST";
-$body = '{"event_type":"charge.succeeded","order":{"order_id":"1790288650833465345","purchase_token":"rT2Et9p0cfzq4fwjrTsGSacq0jQExFDqf5gTy1alp+Y=","client_id":"o6nD4iNavjQj75zPQk","open_id":"4+Axcl2RFgXbt6MZwdh++w==","user_region":"US","goods_open_id":"com.goods.open_id","goods_name":"TestGoodsName","status":"charge.succeeded","amount":"19000000000","currency":"USD","create_time":"1716168000","pay_time":"1716168000","extra":"1111111111111111111"}}';
-$headers = [
- "Content-Type" => ["Content-Type: application/json; charset=utf-8"],
- "X-Tap-Ts" => ["1716168000"],
- "X-Tap-Nonce" => ["V7v7zJ"]
-];
-try {
- $sign = signRequest($method, $url, $body, $headers, $secret);
- echo "Signature: " . $sign . "\n";
-} catch (Exception $e) {
- echo "Error: " . $e->getMessage() . "\n";
-}
-
-```
-
->
-
-
-
-上述示例签算结果
-
-```
-# 参与签算部分
-POST\n
-/my-service/v1/my-method\n
-x-tap-nonce:V7v7zJ\n
-x-tap-ts:1716168000\n
-{"event_type":"charge.succeeded","order":{"order_id":"1790288650833465345","purchase_token":"rT2Et9p0cfzq4fwjrTsGSacq0jQExFDqf5gTy1alp+Y=","client_id":"o6nD4iNavjQj75zPQk","open_id":"4+Axcl2RFgXbt6MZwdh++w==","user_region":"US","goods_open_id":"com.goods.open_id","goods_name":"TestGoodsName","status":"charge.succeeded","amount":"19000000000","currency":"USD","create_time":"1716168000","pay_time":"1716168000","extra":"1111111111111111111"}}\n
-
-# 签算结果 (X-Tap-Sign)
-PyKQzlI65e0I9noVxcQc7FPU3nEyEFHKfRde65F6vhI=
-```
-
-## 通用对象结构描述
-
-### 订单信息
-
-| **参数** | **类型** | **是否必须** | **描述** |
-| -------------- | -------- | ------------ | ------------------------------------------------------------ |
-| order_id | string | Y | 订单唯一 ID |
-| purchase_token | string | Y | 用于订单核销的 token |
-| client_id | string | Y | 应用的 Client ID |
-| open_id | string | Y | 用户的开放平台 ID |
-| user_region | string | Y | [用户地区](/sdk/TapPayments/appendix/regions-currencies) |
-| goods_open_id | string | Y | 商品唯一 ID |
-| goods_name | string | Y | 商品名称 |
-| status | string | Y | [订单状态](/sdk/TapPayments/develop/server/#订单状态) |
-| amount | string | Y | 金额(本币金额 x 1,000,000) |
-| currency | string | Y | [币种](/sdk/TapPayments/appendix/regions-currencies) |
-| create_time | string | Y | 创建时间 |
-| pay_time | string | Y | 支付时间 |
-| extra | string | Y | 商户自定义数据,如角色信息等,长度不超过 255 UTF-8 字符 |
-
-#### 订单状态
-
-| **订单状态** | **描述** |
-| ---------------- | ------------ |
-| `charge.pending` | 待支付 |
-| `charge.succeeded` | 支付成功 |
-| `charge.confirmed` | 已核销 |
-| `charge.overdue` | 支付超时关闭 |
-| `refund.pending` | 退款中 |
-| `refund.succeeded` | 退款成功 |
-| `refund.failed` | 退款失败 |
-| `refund.rejected` | 退款被拒绝 |
-
-### Webhook的事件枚举
-
-| **event_type** | **描述** |
-| ---------------- | -------- |
-| `charge.succeeded` | 充值成功 |
-| `refund.succeeded` | 退款成功 |
-| `refund.failed` | 退款失败 |
-
-## 错误码
-
-| **code** | **描述** |
-| -------- | ------------ |
-| `-1` | 非法请求 |
-| `100000` | 支付服务异常 |
-| `100004` | 订单不存在 |
-| `100018` | 订单验证错误 |
diff --git a/.ci/hk/en/sdk/TapPayments/develop/unity.mdx b/.ci/hk/en/sdk/TapPayments/develop/unity.mdx
deleted file mode 100644
index 6d70ca494..000000000
--- a/.ci/hk/en/sdk/TapPayments/develop/unity.mdx
+++ /dev/null
@@ -1,252 +0,0 @@
----
-title: Unity 集成指南
-sidebar_position: 1
----
-
-
-import MultiLang from "/src/docComponents/MultiLang";
-import sdkVersions from '/src/docComponents/sdkVersions';
-import CodeBlock from '@theme/CodeBlock';
-
-
-## 环境要求
-
-- 支持 Unity 2019.4 及以上版本
-
-## 准备
-
-- 参照 [准备工作](/sdk/start/get-ready/) 所述创建 app,配置 app 参数并且绑定 API 域名
-- 参照 [TapSDK 快速开始](/sdk/start/quickstart/) 配置包名和签名
-
-## 获取 SDK
-
-:::tip
-Unity 2020.3.15 之前的版本为了避免后面构建报错建议升级 Gradle 版本,可参考该 [Gradle 升级步骤文档](/sdk/TapPayments/develop/faq/#1unity-2020315-之前的版本升级-gradle-版本的操作步骤)。
-:::
-
-以下介绍了**通过 Unity Package Manager 导入**或者**修改 Packages/manifest.json 配置文件引入**两种方式,根据项目需要,任选其一即可:
-
-### 1、通过 Package Manager 可视化界面引入
-
-因为 TapPayment SDK 依赖 **EDM4U(External Dependency Manager for Unity)** 来处理 Android 相关的依赖,因此引入依赖的步骤是**先安装 EDM4U 库,然后安装 TapPayment SDK**。
-
-#### 安装 EDM4U
-
-下面介绍了两种方式安装 EDM4U,分别是通过 **OpenUPM 安装**以及**手动下载安装**;
-
-- **方法一:通过 OpenUPM 安装 EDM4U**
-
-EDM4U 可以通过 OpenUPM 进行下载,开发者可以通过 **Edit > Project Settings > Package Manager** 来注册使用 OpenUPM。
-
-![](https://capacity-files.lcfile.com/gpS8BcTVJIdSpMxyDipIWORX4Gb2filA/NdoQbmRRvovet4x3LGUcsTXWnjb.png)
-
-- **方法二:手动下载安装**
-
-开发者可以通过 [Google APIs for Unity](https://developers.google.com/unity/archive#external_dependency_manager_for_unity) 下载 UPM 安装包. 并且通过[从本地磁盘安装](https://docs.unity3d.com/Manual/upm-ui-local.html)的方式进行安装。
-
-#### 安装 TapPayment SDK
-
-TapPayment SDK 可以通过 NPMJS 进行安装, 开发者可以通过 **Edit > Project Settings > Package Manager** 来注册使用 NPMJS。
-
-![](https://capacity-files.lcfile.com/sJDhXK4vAwAYX7BpQFh1SrQzeUmXk165/taptap.png)
-
-<>
- 配置完成以后就可以在 Window > Package Manager > My Registries 中安装 TapTap Payments Global V2 ({sdkVersions.tapGlobalPayments.unity})。
- 如果安装目录中没有 TapTap Payments Global V2, 请尝试在 Edit > Project Settings > Package Manager 中重新注册 NPMJS。
->
-
-![](https://img.tapimg.com/market/images/4900806ff9290049ee68aa67ee05b71b.png)
-
-### 2、修改 Packages/manifest.json 文件
-
-
- {` "dependencies": {
- "com.taptap.tds.payments.global.v2": "${sdkVersions.tapGlobalPayments.unity}", //添加 TapPayment
- "com.unity.purchasing": "3.1.0", //TapPayment 所须依赖
- "com.google.external-dependency-manager": "1.2.179", //TapPayment 所须依赖
- "com.taptap.tds.common":"3.28.3",
- "com.taptap.tds.login":"3.28.3",
- ...
- ...
- },
-
- // 添加 Registries
- "scopedRegistries": [
- {
- "name": "taptap",
- "url": "https://registry.npmjs.org",
- "scopes": ["com.tapsdk", "com.taptap", "com.leancloud"]
- },
- {
- "name": "openupm",
- "url": "https://package.openupm.com",
- "scopes": [
- "com.google"
- ]
- }
- ]`}
-
-
-
-:::tip
-
-**处理安卓依赖失败**
-
-EDM4U 会自动监控 TapPayment SDK 引入的 Android 依赖,但是在某些特殊情况下自动依赖解析可能失败,开发者可以通过 **Assets > External Dependency Manager > Android Resolver > Force Resolve** 来强制依赖解析。在测试之前请确认对于 `com.taptap.android.payment:unity` 的依赖被加到了 mainTemplate.gradle 文件中,如果 EDM4U 没有自动添加这个依赖,开发者也可以通过手动添加的方式处理依赖。
-:::
-
-## SDK 指南
-
-我们在 [Unity IAP](https://unity.com/products/iap) 的框架下实现了一个新的商店实现,通过这种方式降低已经有 Google Play 或者 App Store 内购的 app 的接入成本。如果开发者是第一次接触到内购的流程,我们会在接下来简要说明 Unity 的内购流程,开发者也可以在 Unity 的[官方文档](https://docs.unity3d.com/Packages/com.unity.purchasing@4.8/manual/Overview.html) 中了解有关内购的详细内容。
-
-### 初始化 Unity Gaming Services
-
-调用 `UnityServices.InitializeAsync()` 来一次性初始化 Unity Gaming Services。 这个方法会返回一个 `Task` 对象,开发者可以通过这个对象来获取初始化的状态信息。
-
-```cs
-using System;
-using Unity.Services.Core;
-using Unity.Services.Core.Environments;
-using UnityEngine;
-
-public class InitializeUnityServices : MonoBehaviour
-{
- public string environment = "production";
-
- async void Start()
- {
- try
- {
- var options = new InitializationOptions()
- .SetEnvironmentName(environment);
-
- await UnityServices.InitializeAsync(options);
- }
- catch (Exception exception)
- {
- // An error occurred during services initialization.
- }
- }
-}
-```
-
-### 初始化 IAP
-
-确保你在初始化 IAP 的时候添加 `TapPurchasingModule`,并且设置正确的 `clientId` 和 `clientToken`。
-
-```cs
-using UnityEngine;
-using UnityEngine.Purchasing;
-using TapTap.Payments.Global.V2;
-
-public class MyIAPManager : IStoreListener {
-
- private IStoreController controller;
- private IExtensionProvider extensions;
-
- public MyIAPManager () {
- var builder = ConfigurationBuilder.Instance(TapPurchasingModule.Instance);
- builder.Configure().SetClientId("Your Client ID Here");
- builder.Configure().SetClientToken("Your Client Token Here");
- builder.AddProduct("100_gold_coins", ProductType.Consumable, new IDs
- {
- {"100_gold_coins_google", GooglePlay.Name},
- {"100_gold_coins_mac", MacAppStore.Name}
- });
-
- UnityPurchasing.Initialize (this, builder);
- }
-
- ///
- /// Called when Unity IAP is ready to make purchases.
- ///
- public void OnInitialized (IStoreController controller, IExtensionProvider extensions)
- {
- this.controller = controller;
- this.extensions = extensions;
- }
-
- ///
- /// Called when Unity IAP encounters an unrecoverable initialization error.
- ///
- /// Note that this will not be called if Internet is unavailable; Unity IAP
- /// will attempt initialization until it becomes available.
- ///
- public void OnInitializeFailed (InitializationFailureReason error)
- {
- }
-
- public void OnInitializeFailed(InitializationFailureReason error, string str)
- {
- }
-
-
- ///
- /// Called when a purchase completes.
- ///
- /// May be called at any time after OnInitialized().
- ///
- public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
- {
- return PurchaseProcessingResult.Complete;
- }
-
- ///
- /// Called when a purchase fails.
- ///
- public void OnPurchaseFailed (Product i, PurchaseFailureReason p)
- {
- }
-}
-```
-
-### 发起内购流程
-
-在 IAP 被成功初始化以后,你可以通过调用 `IStoreController.InitiatePurchase` 来发起内购流程。
-
-```cs
-// Example method called when the user taps a 'buy' button
-// to start the purchase process.
-public void OnPurchaseClicked(string productId) {
- controller.InitiatePurchase(productId);
-}
-```
-
-### 处理购买结果
-
-购买完成时会调用商店监听器的 ProcessPurchase 函数。无论用户购买任何物品,您的应用程序都应该履单;例如,解锁本地内容或将购买收据发送给服务器以更新服务器端游戏模型。
-
-此过程会返回结果以指出应用程序是否已完成对购买的处理:
-
-| 结果 | 描述 |
-| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
-| PurchaseProcessingResult.Complete | 应用程序已完成对购买的处理,不应再次向应用程序通知此事。 |
-| PurchaseProcessingResult.Pending | 应用程序仍在处理购买,除非调用 `IStoreController` 的 `ConfirmPendingPurchase` 函数,否则将在下一次应用程序启动时再次调用 `ProcessPurchase`。 |
-
-请注意,在初始化成功后,随时可能调用 `ProcessPurchase`。如果应用程序在 `ProcessPurchase` 处理程序执行过程中崩溃,那么在 Unity IAP 下次初始化时会再次调用它,因此您可能希望实现自己的额外重复数据删除功能。
-
-#### 可靠性
-
-Unity IAP 要求明确确认购买以确保在网络中断或应用程序崩溃的情况下可靠地完成购买。在应用程序离线时完成的任何购买都将在下次初始化时发送给应用程序。
-
-#### 立即完成购买
-
-返回 `PurchaseProcessingResult.Complete` 时,Unity IAP 立即完成交易(如下图所示)。
-
-如果您正在销售可消耗商品并从服务器履行订单(例如,在网络游戏中提供游戏币),那么您不得返回 `PurchaseProcessingResult.Complete`。
-
-否则,如果在保存到云端之前卸载应用程序,则购买的消耗品将面临丢失的风险。
-
-![](https://capacity-files.lcfile.com/PYPIumvCKtaAwhJOPAf1qaApNMOdQ9nF/VrHVbfdKUoCHgXxmOYkccZLMn8e.png)
-
-#### 将购买保存到云端
-
-如果要将消耗品购买交易保存到云端,您必须返回 `PurchaseProcessingResult.Pending`,并且仅在成功保存购买时才调用 `ConfirmPendingPurchase`。
-
-返回 `Pending` 时,Unity IAP 会在底层商店中保持交易为未结 (open) 状态,直至确认为已处理为止,因此确保了即使在消耗品处于此待处理状态时用户重新安装您的应用程序,消耗品购买交易也不会丢失。
-
-![](https://capacity-files.lcfile.com/zDX7oMjWBju6ME0K9i4zym9OOuIgCvrx/TFgMbu4JhohID1xHBnhcet7nnth.png)
-
-## 调试
-**我们当前只提供了 Android 的库实现, 请在 Android 环境下进行各功能调试**(其他平台会逐步补充完善)。
-
diff --git a/.ci/hk/en/sdk/TapPayments/orders-refunds.mdx b/.ci/hk/en/sdk/TapPayments/orders-refunds.mdx
deleted file mode 100644
index c58d21e15..000000000
--- a/.ci/hk/en/sdk/TapPayments/orders-refunds.mdx
+++ /dev/null
@@ -1,53 +0,0 @@
----
-title: 管理订单及处理退款
-sidebar_position: 4
----
-
-
-
-
-
-你可以通过 TapTap 开发者服务中心的「游戏服务」-「TapTap Payments」-「商品与订单」-「交易列表」来查看游戏内购买商品(IAP)的销售订单,并为用户办理退款请求。
-
-![tappay](https://capacity-files.lcfile.com/8WMyxhKLhNA1SniirMSVSOt56kS2z2wb/tappay-orders.png)
-
-## 查找订单
-
-TapTap Payments 支持多种查询方式:
-
-* __按用户标识查询__:输入用户的 TapTap ID,即可查询到当前用户购买的所有订单;
-
-* __按订单号查询__:用户在游戏内购买时创建的 TapTap 订单号;
-
-* __按交易流水号查询__:开发者自定义的订单 ID;
-
-* __按订单状态查询__:根据订单交易状态查询,包括“支付中”、“已支付”、“已验证”、“已过期”、“已退款”等;
-
-* __按订单时间查询__:筛选用户订单发生的日期查询;
-
-## 订单状态
-
-订单页面会显示每一笔用户购买的状态,如下:
-
-| 订单支付状态 | 说明 |
-| ---- | ---- |
-| 支付中 | 用户还未完成付款,或系统正在处理订单 |
-| 订单已支付 | 用户完成付款,系统已成功向用户收款 |
-| 订单已过期 | 订单长时间未进行付款,或付款已取消 |
-
-| 订单退款状态 | 说明 |
-| ---- | ---- |
-| 订单退款中 | 用户的退款申请已被允许,系统正在处理退款 |
-| 订单已退款 | 系统已全额退款至用户支付账户 |
-| 订单已驳回 | 用户的退款申请不被允许 |
-| 订单退款失败 | 系统退款不成功,或用户的付款方式遭到拒绝 |
-
-## 办理退款
-
-为了保障和维护用户的合理权益,TapTap 为用户提供退款的服务。当用户向 TapTap 发起退款申请时, TapTap 会向开发者收集用户消费及物品消耗的材料,用于是否允许退款的决策判断。你可在「游戏服务」-「TapTap Payments」-「处理退款」查看到用户提交的退款请求:
-
-* __退款__:同意 TapTap 全额退款给用户,系统将用户支付金额全部退回至用户的付款账户。退款后,你有责任告知用户对游戏内商品的处理方式。
-
-* __驳回__:不同意 TapTap 退款给用户,你必须向 TapTap 和用户说明不允许退款的合理原因,或出示用户已消费的相关凭证。
-
-如若开发者消极处理或不予处理,TapTap 拥有最终决策处理权。
diff --git a/.ci/hk/en/sdk/TapPayments/overview.mdx b/.ci/hk/en/sdk/TapPayments/overview.mdx
deleted file mode 100644
index 936668ef4..000000000
--- a/.ci/hk/en/sdk/TapPayments/overview.mdx
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title: 概述
-sidebar_position: 1
----
-
-# 概述
-
-## **什么是 TapTap Payments?**
-
-TapTap Payments 是一项付款服务,利用该服务可以在 TapTap 上架的游戏中销售各种商品
-
-- **触达全球受众:**我们提供多种[全球主流支付方式](/sdk/TapPayments/appendix/payment-methods/),覆盖 [173 个国家和地区](/sdk/TapPayments/appendix/regions-currencies),支持 42 种货币,触达 TapTap 千万硬核玩家
-- **88%/12% 收入分成:**获得 88% 的收入分成,更具竞争力的价格,释放全球收入潜力
-- **流畅接入:** 提供简洁、易用、稳定的 SDK,让开发者快速接入支付功能,节省开发时间和成本
-- **全球合规:**经验丰富的全球财税法经验,符合各地合规标准,让您专注于游戏开发与变现
-
-通过在你您的游戏中集成 TapTap Payments,您可以销售两种不同类型的游戏内商品:
-
-- **消耗型商品:**消耗型商品是指当用户消耗该商品时,游戏会分配相关联的游戏内容,而用户随后可以再次重复购买的商品。例如金币、道具等。
-- **非消耗型商品:**非消耗型商品是指购买一次便能永久使用的商品。例如付费升级和关卡包等。
-
-## **销售游戏内商品的三个基本步骤**
-
-**第一步 申请商业卖家身份**
-
-在您可以对您的游戏内商品收费之前,需要通过我们的商务伙伴,申请商业卖家身份并注册您的付款信息,完成审核后将为您开通相关功能
-
-**第二步 使用我们的 SDK 开发您的游戏 **
-
-下载 TapTap Payments SDK 并将其集成到您的游戏中,我们提供了 Unity 和 Android 的集成选项:
-
-- [Unity 集成指南](/sdk/TapPayments/develop/unity)
-- [Android 集成指南](/sdk/TapPayments/develop/android)
-
-**第三步 为您的游戏添加商品**
-
-在 [TapTap 开发者中心](https://developer.taptap.io/)添加商品并注册相关信息:项目 ID、项目标题、项目类型、价格等。进一步了解:
-
-- [在开发者中心管理商品](/sdk/TapPayments/product-management)
diff --git a/.ci/hk/en/sdk/TapPayments/payout.mdx b/.ci/hk/en/sdk/TapPayments/payout.mdx
deleted file mode 100644
index def5cc958..000000000
--- a/.ci/hk/en/sdk/TapPayments/payout.mdx
+++ /dev/null
@@ -1,43 +0,0 @@
----
-title: TapTap 付款
-sidebar_position: 5
----
-
-
-
-
-
-TapTap 付款仅用于 TapTap Payments 服务中,按照一定周期的付款时间为开发者结算收益,并付款至开发者的财务账户。在准备接收 TapTap 的付款之前,请先确认已完成财务付款资料:
-
-1. TapTap 开发者服务中心 -「财务与管理」-「财务主体」;
-
-2. 填写主体信息与财务账户信息;
-
-## 付款周期和报告
-
-TapTap 的付款周期通常为自然月结束后的 45 天,例如自然月 1 月份( 1 月 1 日 - 1 月 31 日)的收入,会在 1 月 31 日结束之后,进入核算阶段,并在此后 45 天内完成付款,即 3 月 15 日是 TapTap 的最晚付款时间。请注意你的银行可能需要几天的时间才能将相应款项计入你的账户。
-
-在进入核算阶段后,你可在「财务与管理」-「对账与结算」看到需要结算的报告单,包括在当前账期内,商品销售金额、用户退款金额、费用扣除、实际收益金额等数据。你必须对报告单进行仔细的核对,并在 7 个工作日之内与 TapTap 确认最终数目。
-
-![tappay](https://capacity-files.lcfile.com/kmsrDieltrX4CXHUwcIwfMlrWLtuxsN6/tappay-dc-bill.png)
-
-## 状态说明
-
-TapTap 根据游戏销售金额,退款、当地税费、支付渠道手续费等进行核对结算,计算出开发者所得金额。核算周期为每月一次,在每个自然月结束之后进入核算阶段。
-
-* __待核算__:等待 TapTap 核算完成。
-
-* __待确认__:TapTap 已经核算完成的账期,需要开发者确认核算结款金额。
-
-* __待付款__:等待 TapTap 付款。
-
-* __已付款__:TapTap 已将账单开发者实际收益金额付款至开发者预留的银行账户。
-
-## 付款最低限额
-TapTap 付款的最低金额是 100 美元,如果你在上个月度的销售额没有超过 100 美元,则会累计到下个月度一并发放。如果你的销售额超过了 100 美元,但出于对服务费或跨境税的考虑,你也可以向 TapTap 申请累计到一定额度后付款。
-
-## 付款货币
-TapTap 统一以“美元”进行付款。付款时由于接收地区或银行的不同,可能会产生一些跨境费用或转账手续费用,请联系你所在银行查看。
-
-## TapTap 的服务费
-开发者使用 TapTap Payments 服务,TapTap 会针对上述内容收取服务费用。
diff --git a/.ci/hk/en/sdk/TapPayments/product-management.mdx b/.ci/hk/en/sdk/TapPayments/product-management.mdx
deleted file mode 100644
index 8978f96fc..000000000
--- a/.ci/hk/en/sdk/TapPayments/product-management.mdx
+++ /dev/null
@@ -1,83 +0,0 @@
----
-title: 商品管理
-sidebar_position: 3
----
-
-
-
-
-
-若要在游戏中提供内购商品,必须在 TapTap 开发者服务中心录入内购商品信息,并添加到你的游戏内。每个商品须关联一个游戏应用(Client),并且只能用于该游戏。在使用过程中,你可以创建或删除商品,也可以修改完善现有商品的信息。
-
-## 创建游戏内商品
-
-在创建商品之前,请务必仔细规划您的产品 ID。商品 ID 对于您的游戏来说必须是唯一的,并且在创建后不能更改或重复使用。
-
-- 商品 ID 必须以数字或字母开头,长度不超过 30 个字符,可以包含数字 (0-9)、英文字母 (a-z/A-Z)、下划线 (_) 和句点 (.)
-- 创建商品后,您无法更改或重复使用商品 ID
-
-### 创建单个商品
-
-1. 前往 TapTap 开发者服务中心,选择「游戏服务」-「TapTap Payments」-「商品与订单」;
-
-2. 点击「添加商品」,在弹出的表单中填写您的商品详细信息;
-
- - 商品类型:消耗型和非消耗型商品,详情请参见[商品类型](/sdk/TapPayments/overview)
- - 商品 ID:商品的唯一标识,创建后不可更改和重复使用;
- - 商品名称:商品的短名称,用户可见。建议保持在 80 个字符以内;
- - 商品描述:对商品内容的细节描述,建议保持在 100 个字符以内;
- - 商品定价:期望售卖的价格,默认以美元定价,开发者可下载所有定价等级查看不同国家的价格;
-
-3. 完成信息填写后,点击「提交审核」并等待审核结果;
-
-4. 审核通过后,如需商品生效,可点击商品对应操作列的「上架」按钮即可;
-
-
-> 注意同一个游戏内商品 ID 不能重复,提交后将不可更改,删除后也无法重复使用,请仔细规划。商品在未上架状态时均可修改名称、描述、价格等信息,上架后的商品不可修改信息。具体见「编辑/修改商品」。
-
-
-![mahua](https://capacity-files.lcfile.com/GSpWNUzJ025ypkp42jD180Ko7u9wDiUA/add_sku.png)
-
-## 批量创建多个商品
-
-1. 如需同时创建多个游戏内购商品,您可以选择使用「批量导入」功能,上传包含每个商品详细信息的 CSV 文件;
-
-2. 前往 TapTap 开发者服务中心,选择「游戏服务」-「TapTap Payments」-「商品与订单」;
-
-3. 点击「批量导入」:
-
- - 下载模版;
- - 上传商品详细信息 CSV 文件;
- - 请确保每个商品都为单独一行,单次上传不得超过 100 个商品,下载模板;
- - 当商品详细信息 CSV 中的商品 ID 与后台列表中现有商品 ID 匹配时,此次商品详细信息 CSV 中的商品不被上传;
-
-> 商品详细信息 CSV 文件上传成功后,系统将直接提交审核,若上传了系统不支持的语言,则会忽略该语言内容并由默认语言替代。
-
-![tappay](https://capacity-files.lcfile.com/6GwDJdk5zVDaVp1znwrPRUEx1TcmjQC5/add_sku_batch.png)
-
-## 编辑/修改商品
-
-| 商品状态 | 编辑与操作 |
-| ---- | ---- |
-| 审核通过,待上架 | 可修改商品名称、商品说明 / 可上架 / 可删除 |
-| 审核失败 | 可修改商品名称、商品说明 / 可删除 |
-| 已上架 | 可修改商品名称、商品说明 / 可下架 |
-| 已下架 | 可删除 |
-
-## 商品状态说明
-
-* __审核中__:商品在发布之前需要提交审核,审核通过则进入待上架状态,审核失败会给出失败原因,需要根据失败原因进行修改后重新提交;
-
-* __审核通过__:审核通过的商品,处于待上架状态,可随时发布上架,上架的商品才会在生产环境生效;
-
-* __已上架__:上架后的商品,会在生产环境生效;
-
-* __已下架__:下架的商品,无法在生产环境使用。如用户点击该商品后提示“该商品已下架”;
-
-* __已删除__:开发者可以删除商品,删除的商品不可再复用;
-
-## 语言和本地化
-
-如果你的游戏会在不同的国家/地区发布,那么也建议为你的商品添加本地化信息。通过添加不同地区的语言完成本地化信息的录入。
-
-![tappay](https://capacity-files.lcfile.com/RuxW5n9niJRDFIVVAa2uOURPHbnpgx4F/product-multilingual.png)
diff --git a/.ci/hk/en/sdk/embedded-moments/features.mdx b/.ci/hk/en/sdk/embedded-moments/features.mdx
deleted file mode 100644
index 4317e639d..000000000
--- a/.ci/hk/en/sdk/embedded-moments/features.mdx
+++ /dev/null
@@ -1,138 +0,0 @@
----
-title: Embedded Moments Features
-sidebar_label: Features
-sidebar_position: 1
----
-
-## Introduction
-
-By adding Embedded Moments to your game, you can have players access TapTap’s forum without leaving the game so they can easily browse walkthroughs, share their game highlights, and interact with other players and officials.
-
-## Core Advantages
-
-**For game developers:**
-
-- Allow players to share their gameplay with just a single click.
-- Display officially published content on players’ feeds.
-- View what players have posted and provide timely feedback to them.
-
-**For game players:**
-
-- Communicate and interact with other players on a delayed basis while gaming.
-- Look up walkthroughs and top players’ solutions when stuck on certain scenes in the game.
-
-## Account System
-
-For a player to make a post or interact with other posts using Embedded Moments, they have to log in to a TapTap account. Therefore, you need to enable **[TapTap Login](/sdk/taptap-login/features/)** for your game before you can use Embedded Moments.
-
-![](https://capacity-files.lcfile.com/8C2DjigrigAz03drPAO3U49BcntTMhNz/login.png)
-
-## Moments
-
-### Games
-
-A player can access the TapTap forum directly from the “Games” module:
-
-- **Banners**: Banners can help you convey important notifications and events to players. This is a module exclusive to Embedded Moments. You can edit banners in “Gaming Ecosystem > Embedded Moments > Banner Configuration” and the edits will be displayed to the players once they are approved.
-- **Feeds**: By default, a player will see the recommended posts when they open Embedded Moments.
-
-![](https://capacity-files.lcfile.com/vLkyj72lfbsuh1sv7fQOsrNFrsSEHJvp/Games.png)
-
-**Posting moments**: Players can post moments containing pictures and videos to the forum.
-
-![](https://capacity-files.lcfile.com/E9WodoFbRgquQaGG9rML5V4K9eUwYSqq/take_post.png)
-
-- **Interactions**: Players can **like, comment, and repost** other players’ moments.
-
-![](https://capacity-files.lcfile.com/NFiIX2c86mODiRJeLEk5Qw9F0zimdzPD/reply.png)
-
-### Feeds
-
-Players who logged in to TapTap can view the moments posted by their friends and the official accounts. When there are new moments available, there will be a **badge** on the “Follow” section on the navigation bar. This ensures that players will never miss out on important updates.
-
-![](https://capacity-files.lcfile.com/dY9bMBXUxolz6FL3fDO9cggQ1cFo7tjN/Following.png)
-
-### Messages
-
-Here players can view their messages triggered by events like interactions between players.
-
-![](https://capacity-files.lcfile.com/Uym4pMC7RV09OKcYdXRuFBdmJqhyAz3k/Notification.png)
-
-### Profile Page
-
-Players can find their past moments on the “Me” page. Here players can share their moments on other apps or delete their moments.
-
-![](https://capacity-files.lcfile.com/fACDedkGs1dbMAgFr2jyVU7w0k4fWCik/Me-global.png)
-
-### Search
-
-Players can search within Embedded Moments. Their search histories will be preserved so the system can give recommendations to the players.
-
-![](https://capacity-files.lcfile.com/1NB8Gzr77x8Xiybw6qd7EF8WaWOUuUiz/search.png)
-
-## SDK Features
-
-### Scenario-Based Portals
-
-You can make any of the objects in your game as a portal that opens up Embedded Moments. You can even specify landing pages for certain scenes in the Developer Center. This could be helpful if you want to allow players to quickly get help from the community when they’re stuck on certain scenes in the game.
-
-![](https://capacity-files.lcfile.com/ofMVqjfGnTpyvuYmesid6JdraU6pHIHX/FlashPartyHeroGuide.png)
-
-:::tip
-
-1. TDS doesn’t provide any guidelines for the design of portals. We encourage you to design your portals so they look harmonious with the scenes they’re placed at.
-2. The landing page of a portal can be set up to be an article or a specific module according to your own needs.
-
-:::
-
-### Badges
-
-You can place buttons that can display badges in your game so that the players can be attracted to open the Embedded Moments when they see the badges.
-
-![](https://capacity-files.lcfile.com/ppMOzFxF2KRvfSbpt4bhPzfxuRUwGl51/Badges.png)
-
-:::tip
-
-1. Using badges can help you increase the chance for players to open the Embedded Moments. We encourage you to place buttons with badges on prominent places in your game.
-2. The badges here share the same logic as the badges for the “Follow” module within the Embedded Moments. The new content posted by the users followed by the players will trigger a notification, and the interval of retrieving new notifications is once per minute (here 1 minute is the minimum interval; you can change it to 3 minutes, 5 minutes, etc.).
-3. Once the player opens the Embedded Moments, the game needs to clear the badge and continue inquiring for the next display of the badge.
-
-:::
-
-### Quick Sharing
-
-Players can take screenshots within the game and quickly share them on Embedded Moments. Only text and images can be shared through this method.
-
-![](https://capacity-files.lcfile.com/qQu2WSd7lft6N2Ga62MKhbEkMkU9JhXK/share_data.gif)
-
-### Pop-up for Dynamically Closing Embedded Moments
-
-While the player is browsing Embedded Moments, if there are events that demand the player to immediately return to the game, a pop-up can be displayed to serve as a reminder and offer a shortcut for the player to close the Embedded Moments.
-
-![](https://capacity-files.lcfile.com/hBYaymebMR3iwSdFig6W53Gm2LHwzf6h/ClosingEmbeddedMoments.png)
-
-## Administration
-
-### Theme Configuration
-
-To have Embedded Moments fit better with the game scenes and not make players feel cut off, TDS allows you to customize the theme of the Embedded Moments. You can upload a background image and specify the colors of texts in “Game Services” > “Embedded Moments” > “Theme”.
-
-![](https://capacity-files.lcfile.com/cKlYPD05CODMXMbG6ygmxPhPpiHBGVeE/ThemeConfiguration.png)
-
-If the game only supports landscape _or_ portrait mode, you only need to provide one background image. Otherwise, you need to provide background images for both.
-Images are subject to review, which usually takes 2 business days.
-
-### Banner Configuration
-
-You can set up banners in Embedded Moments to help you broadcast your events to the players. To set up banners, go to “Game Services” > “Embedded Moments” > “Banner Configuration”. **A title, background image, and link** are required for each banner.
-
-![](https://capacity-files.lcfile.com/uMGtukhn1wQQmLmJ0icNK6eJALcW7Yae/BannerConfiguration.png)
-
-You can add up to 5 banners that link to any website.
-Banners are subject to review, which usually takes less than one day.
-
-### Scenario-Based Portal Configuration
-
-You can set up scenario-based portals in “Game Services” > “Embedded Moments” > “Scenario-based Portal”. Once you submit a **portal name, landing page type, and landing page**, you can use the generated portal ID in your game. This module doesn’t require any reviews, and you are free to change the landing page of each portal.
-
-![](https://capacity-files.lcfile.com/VATMXxjDD1U1OihW705a7BpuQgFfL1b4/Scenario-BasedPortalConfiguration.png)
diff --git a/.ci/hk/en/sdk/push/guide/android-mixpush.mdx b/.ci/hk/en/sdk/push/guide/android-mixpush.mdx
deleted file mode 100644
index 37e40be8d..000000000
--- a/.ci/hk/en/sdk/push/guide/android-mixpush.mdx
+++ /dev/null
@@ -1,221 +0,0 @@
----
-title: FCM Push Guide
-sidebar_label: FCM Push
-sidebar_position: 4
-slug: /sdk/push/guide/android-mixpush/
----
-
-
-
-
-
-import CodeBlock from '@theme/CodeBlock';
-import sdkVersions from '/src/docComponents/sdkVersions';
-
-## FCM Push Overview
-
-Since Android 8.0, system permissions have been tightened and the lifecycle of third-party push channels has become more limited. This is why we have introduced our FCM push solution. It guarantees push delivery rate on mainstream Android systems.
-
-In the FCM push solution, the channel used for message delivery is no longer the long WebSocket connection that we maintain ourselves, but the communication is done through FCM.
-A push message is sent in the following way:
-
-1. The developer calls the push API to request a push to all or specific devices;
-2. The cloud push server forwards the request to the FCM;
-3. The FCM sends the push message through the system channel to the mobile phone, while the system message receiver on the mobile phone displays the push message in the notification bar;
-4. The end user clicks on the message and the target application or page is launched.
-
-The whole process is similar to Apple's APNs push, and the SDK is basically not called on the client side.
-
-The Android FCM push feature is only available for apps with Business Plans. If you would like to use this feature, please go to **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Settings > FCM** and enable FCM Push.
-
-Note that FCM push can be turned on and off at any time. If this option is turned off, the next Android push will automatically be sent to the client through our own channel, just like a normal push, with no effect other than the above mentioned issue of our own channel being limited on some ROMs. If this option is enabled again, the push channels of the vendors will be used again.
-
-When FCM push is enabled, a `registrationId` field is added to each device record in the Installation table to record the vendor-assigned registration ID (similar to the device token in APNs), and a `vendor` field is added (if this field does not exist, there is a client integration problem) with a value of `fcm`.
-
-### Displaying Badges or Push Notifications
-
-A badge appears on the application icon when there are new notifications.
-
-### Notification Bar Messages and Pass-Through Messages
-
-FCM supports pass-through messages to applications when the application is in the foreground.
-
-### Offline Push for Instant Messaging
-
-In the Instant Messaging service, offline pushing is available on iOS when the user goes offline. For Android users, there is no offline pushing when using the private channel because chat and push share the same long WebSocket connection, and if the user goes offline in the Instant Messaging service, pushing is necessarily unreachable. However, if FCM push is enabled, Android users have an offline push notification path because the push message goes through FCM, which is essentially the same as on iOS.
-This means that when FCM push is enabled, the offline push and mute mechanisms in Instant Messaging are also available to Android users who use FCM push.
-
-### Limitations
-
-Push message length limit:
-
-- Messages support a maximum of 128 bytes for the application package name and 4KB for the message content.
-
-Minimum Android version requirement:
-
-- FCM push supports Android 4.1 or higher (minSdkVersion: 16).
-
-Description of factors that affect delivery rates:
-
-- Whether the device is online or not. If the device is offline, the push service will cache messages and push them to the device when it is online.
-- Whether the application that integrates the Push Notification SDK is uninstalled on the mobile device.
-- Whether the network status of the mobile device is stable.
-- The security control policy of the mobile device.
-- The delivery of transmissions is affected by the Android system and whether the application is running in the background.
-
-## Integration
-
-FCM push is essentially dependent on the FCM SDK and server-side capabilities. Our client SDK is a wrapper around the FCM SDK and the actual push requests are passed through LeanCloud to the FCM backend. Our client-side SDK may not be able to keep up with the iteration speed of the FCM, so we recommend that you interface directly with the FCM SDK and store the FCM-assigned "registration id" and FCM identifier (see vendor in previous chapter) in the device information table (`Installation`) so that you can then use our push API to correctly send push messages to all devices. This will allow us to send correct push messages to all devices via our push API.
-
-### Integration on the Client Side
-
-The developer inherits their implementation class from `FirebaseMessagingService` and then calls the code in the `onNewToken` callback function to save it as in the example above (remember to replace `vendor` with `fcm`). See [LCFirebaseMessagingService](https://github.com/leancloud/java-unified-sdk/blob/master/android-sdk/leancloud-fcm/src/main/java/cn/leancloud/LCFirebaseMessagingService.java#L69) for sample code.
-
-### Server-Side API for Sending FCM Pushes
-
-See [Push Notification REST API Guide](/sdk/push/guide/rest/).
-
-If you want to integrate our packaged FCM push SDK, you can read on, but if you want to access the FCM SDK yourself, you can ignore the following.
-
-## FCM Push Library Components
-
-[FCM](https://firebase.google.com/docs/cloud-messaging) (Firebase Cloud Messaging) is a service provided by Google/Firebase to send push notification messages to mobile phones. When integrating, the backend must configure the push key and certificate needed to connect to the FCM server, and the FCM-related token is applied by the LeanCloud SDK.
-
-### Environment Requirements
-
-The FCM client must run on a device running Android 4.1 or higher with the Google Play Store application installed, or in an emulator running Android 4.1 with Google API support. See [Set up a Firebase Cloud Messaging client app on Android](https://firebase.google.com/docs/cloud-messaging/android/client) for specific requirements.
-
-### Integrate the SDK
-
-#### Add Firebase Configuration File
-
-Download the latest configuration file (google-services.json) from the Firebase console and add it to the module (application level) directory of the application.
-
-#### Add Google Service Plugin to Gradle
-
-First, add rules to the root (project level) Gradle file (build.gradle) to include the Google services Gradle plugin:
-
-```groovy
-buildscript {
-
- repositories {
- // Check that you have the following line (if not, add it):
- google() // Google's Maven repository
- }
-
- dependencies {
- // ...
-
- // Add the following line:
- classpath 'com.google.gms:google-services:4.3.5' // Google Services plugin
- }
-}
-
-allprojects {
- // ...
-
- repositories {
- // Check that you have the following line (if not, add it):
- google() // Google's Maven repository
- // ...
- }
-}
-```
-
-Then, in the module (application level) Gradle file (usually app/build.gradle), apply the Google services Gradle plugin:
-
-```groovy
-apply plugin: 'com.android.application'
-// Add the following line:
-apply plugin: 'com.google.gms.google-services' // Google Services plugin
-
-android {
- // ...
-}
-```
-
-#### Import SDK FCM Package
-
-In the module (application level) Gradle file (usually app/build.gradle), add the dependencies to the dependencies:
-
-
-{`dependencies {
- implementation 'cn.leancloud:leancloud-fcm:${sdkVersions.leancloud.java}@aar'
- // For Instant Messaging and Push Notification
- implementation 'cn.leancloud:realtime-android:${sdkVersions.leancloud.java}'
- implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'\n
- // Import the BoM for the Firebase platform
- implementation platform('com.google.firebase:firebase-bom:27.0.0')
- // Declare the dependencies for the Firebase Cloud Messaging and Analytics libraries
- // When using the BoM, you don't specify versions in Firebase library dependencies
- implementation 'com.google.firebase:firebase-messaging'
- implementation 'com.google.firebase:firebase-analytics'
-}`}
-
-
-#### Modify the Application Manifest
-
-Add the following to your application's `AndroidManifest` file:
-
-- PushService service.
-
- ```xml
-
- ```
-
-- `LCFirebaseMessagingService` service. This service must be added if you want to do any message processing in the background other than receiving application notifications. To receive notifications, receive data payloads, send uplink messages, etc. in the foreground application, you must inherit this service.
-
- ```xml
-
-
-
-
-
- ```
-
-- (Optional) The metadata element in the application component used to set the default notification icon and color. If the incoming message does not explicitly set an icon and color, Android uses these values.
-
- ```xml
-
-
- ```
-
-- (Optional) As of Android 8.0 (API level 26), Android supports and recommends the use of [notification channels](https://developer.android.com/develop/ui/views/notifications#ManageChannels). FCM provides default notification channels with basic settings. If you want to [create](https://developer.android.com/develop/ui/views/notifications/channels) and use your own default channels, please set `default_notification_channel_id` to the ID of your notification channel object (as shown below); if the incoming message does not explicitly set a notification channel, FCM will use this value.
-
- ```xml
-
- ```
-
-- If FCM is critical to the functionality of your Android app, be sure to set `minSdkVersion 16` or higher in the app's `build.gradle`. This will ensure that the Android app cannot be installed in an environment that does not allow it to function properly.
-
-### Application Initialization
-
-With FCM push, no special initialization is required for the client application. If the registration is successful, a new record should appear in the `_Installation` table with the field **vendor** as `fcm`.
-
-### Configure the Console (Set FCM's ProjectId and Private Key)
-
-You can get the private key file for the server side to send push requests in [Firebase Console](https://console.firebase.google.com/). Associate this file and the ProjectId with the cloud service application via **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Settings > FCM**.
-
-## Unregister FCM Push
-
-For users who are already registered for FCM push, if they want to unregister for FCM push and use the cloud service's own WebSocket instead, they can call the following function:
-
-```java
-LCMixPushManager.unRegisterMixPush();
-```
-
-This function is asynchronous. If the deregistration is successful, the log will say `Registration canceled successfully`. If the deregistration fails, the log will say `unRegisterMixPush error`.
-
-## Suggestions for Troubleshooting
-
-- If there are conditions that are not met when registering, the SDK will log the reason for the registration failure, for example `register error, mainifest is incomplete` means the manifest is not filled correctly. If the registration is successful, the corresponding record in the `_Installation` table should have the field **vendor** that is not null.
-
-- If the registration continues to fail, please submit a ticket or post to the forum with the relevant logs, device model, and system version number, and we will follow up to assist in troubleshooting.
diff --git a/.ci/hk/en/store/store-admin.mdx b/.ci/hk/en/store/store-admin.mdx
deleted file mode 100644
index 3867c654d..000000000
--- a/.ci/hk/en/store/store-admin.mdx
+++ /dev/null
@@ -1,134 +0,0 @@
----
-title: Authority Management
-sidebar_position: 25
----
-
-import {Red, Blue, Black, Gray} from '/src/docComponents/doc';
-
-## Add Member or Master Administrator
-
-Super administrators have all the authorities as a provider and authorities for a specific game. Only super administrators can add new members to a game team or assign game management authorities.
-
-Go to Authority Management >> Click on Add User >> Enter user’s nickname or TapTap ID >> Select ‘Members’ or ‘Master Administrator’ for Role Type.
-
-![ ](/img/Administrator-Settings-1.png)
-
-![ ](/img/Administrator-Settings-2.png)
-
-## Role Configuration
-
-### View authority of default roles
-
-In Authority Management >> Role Configuration, there is a set of default roles that are commonly used such as developer, publisher and the game’s administrator. The authorities of default roles can be found in ‘View Role’ and cannot be changed.
-
-![ ](/img/Administrator-Settings-3.png)
-
-![ ](/img/Administrator-Settings-4.png)
-
-### Add Roles
-
-There might be some inconvenience in the actual work as the authority of default roles cannot be changed. In this case, go to Add Role and find two types of roles: the Game’s Role that deals with one specific game and the Provider’s Role that deals with multiple games of the same provider. After adding a new role, Master Administrator may name the role and select the authority according to the actual needs.
-
-![ ](/img/Administrator-Settings-5.png)
-
-![ ](/img/Administrator-Settings-6.png)
-
-## Role Configuration for Members
-
-### Provider’s Authority
-
-A member can obtain the provider’s authorities when necessary. For example, while provider administrator may manage the release and update of the game, members of finance role may focus on the orders, settlement, and reconciliation, etc.
-
-Go to Authority Management >> Select user >> Click on Edit Authority to set up this user’s authority.
-
-![ ](/img/Administrator-Settings-7.png)
-
-![ ](/img/Administrator-Settings-8.png)
-
-![ ](/img/Administrator-Settings-9.png)
-
-### Member’s authorities for a specific game
-
-The super administrator can assign the authorities for one or more games to the corresponding members at the same time.
-
-![ ](/img/Administrator-Settings-10.png)
-
-The authority of members for each game can be set separately.
-
-![ ](/img/Administrator-Settings-11.png)
-
-## Delete Member
-
-Go to Authority Management >> Find the user >> Click on ![‘deleteadmin’](https://img.tapimg.com/market/images/2e5c836549d866d6d44036d158095cbb.png). A pop-up window will appear, where the user can be added as a Master Administrator or be deleted. Notice that only Super Administrator can make this action.
-
-![ ](/img/Administrator-Settings-12.png)
-
-## Master Administrator of the forum (Group)
-
-### Add Master Administrator
-
-The master administrator of the Game’s Group has the highest management authority. Each game can be linked to one master administrator of the Group. You can link it by going to Group Settings >> Click on Tap.io to link with a user
-
-![ ](/img/Administrator-Settings-13.png)
-
-### Access Group Management Page
-
-After you have set up the Master Administrator of the Group, you can go to the game page >> click on the Group tab and click on Show All on the upper right corner to access the Group’s management features.
-
-![ ](/img/Administrator-Settings-14.png)
-
-![ ](/img/Administrator-Settings-15.png)
-
-## Q&A
-
-### 1. Can the Super Administrator be changed? Is it possible to set up a new Super Administrator?
-
-Yes.
-
-Send your request to [international_operation@taptap.com](mailto:international_operation@taptap.com). Please describe your needs and provide a valid proof that you are the developer of this game, or you have the right to make such change.
-
-**Template**
-
-> **Subject:**
-> Unlink TapTap Super Admin - XXX (provider title)
-
----
->
-> The title or the link to the page of the provider that needs to change the Super Administrator:
->
-> Proof that you are the developer of this game, such as a scanned copy of the Business License or other documents (this can be included in the attachment).
->
-> Briefly explain why you need to make this change:
->
-> Contact of the applicant:
-
-Our staff will contact you within 2 business days after receiving your email. Please check your inbox regularly.
-
-After the original Master Administrator is deleted, you can submit the developer application in the name of the same provider. Once your application is approved, you will become the new Super Administrator of this provider account by default.
-
-### Can I change the linked email?
-
-Yes.
-
-Please make sure that the new email address has not been linked with any TapTap account. Please send your request to operation@taptap.com with proof that you or your organization is the owner of this account.
-
-
-**Template**
-
-> **Subject**
-> Change Linked Email - XXX (title of provider/game)
-
----
->
-> TapTap ID whose linked email needs to be changed:
->
-> Old email address (can be empty):
->
-> New email address:
->
-> Briefly explain the reason for this change:
->
-> Proof of you as the developer:
-
-After receiving your email, our staff will forward your application to the relevant team, which will process within 2 business days. Once the process is completed, you will be able to receive verification codes with the new email address.
-
diff --git a/.ci/hk/en/store/store-agree.mdx b/.ci/hk/en/store/store-agree.mdx
deleted file mode 100644
index 56885547c..000000000
--- a/.ci/hk/en/store/store-agree.mdx
+++ /dev/null
@@ -1,573 +0,0 @@
----
-title: TapTap Game Review Specifications and Rules
-sidebar_position: 20
----
-
-
-
-Please make sure that the game you submit in the Developer Center conforms to the TapTap Game Review Specifications and Rules. Games that violate the above rules may be removed from TapTap.
-
-
-
-## **Overview**
-
-
-
-### 1.1 Laws and Regulations
-
-
-
-1.1.1
-Your game cannot contain any illegal content or other sensitive information, including but not limited to information regarding politics, gambling, violence, gore, animal or child abuse and sexual content.
-
-
-
-1.1.2
-Your game shall not harass or intimidate an individual or group of individuals. It shall not infringe on the legitimate rights and interests of an individual or group of individuals (including but not limited to the copyright, trademark rights, portrait rights, rights to reputation, etc.)
-
-
-
-1.1.3
-Your game shall not make overly realistic representations of weapons. For example, it must not address the manufacturing process and parameters of weapons. Your game shall not promote violation of laws or misuse of weapons.
-
-
-
-1.1.4 Your game shall not violate any legal requirements in any region where it is made available.
-
-
-
-
-1.1.5
-The platform has the right to refuse to release games that contain security risks, such as information that convenes, promotes, instigates crimes, or is suspected of violating laws and regulations.
-
-
-
-### 1.2 In-app Purchases
-
-
-
-1.2.1
-The price of all in-app purchases in your game must be clearly marked, and the services or content to be provided must also be clearly described. If the actual service or content does not match the description, your game may be removed, and your account may be suspended.
-
-
-
-1.2.2
-The game must not induce unprotected purchases in any form, including but not limited to direct payments outside the site, offline trades, and crowdfunding by QR code, etc.
-
-
-
-1.2.3 Your game shall not make automatic deductions of fees.
-
-
-
-### 1.3 In-game Advertising and Bundleware
-
-
-
-1.3.1
-Advertisements in your game cannot contain any illegal content or other sensitive information, including but not limited to information about politics, gambling, violence, gore, animal or child abuse, sexual suggestive content, and any expressions prohibited the advertising law.
-
-
-
-1.3.2 Advertisements in your game must not mislead users by imitating system notifications or prompts.
-
-
-
-1.3.3 Advertisements in your game shall not continue to exist when your game is closed or is running the back end.
-
-
-
-1.3.4 There shall not be any hover or pop-up advertisements that cannot be closed in your game.
-
-
-
-1.3.5
-The advertisements in your game cannot include any bundled downloads. For example, to proceed to the game's login and registration page, users should not have to download any other applications .
-
-
-
-1.3.6 Your game shall not force users to download other applications before they can play.
-
-
-
-### 1.4 Bundlewares
-
-
-
-1.4.1 Your game’s start screen should not have an option that is checked by default to allow downloading other applications without the user's permission. If there are other options checked by default, users must be clearly notified.
-
-
-
-1.4.2 Your game cannot automatically download other applications without the user's permission.
-
-
-
-### 1.5 Repetitions
-
-
-
-1.5.1 Do not upload multiple games of the same or similar content. Repetitive games may be removed.
-
-
-
-1.5.2
-Key features of the update of your game must not differ too much from the last version. For example, if the previous version is a text-based game, the new version cannot be updated to an action game.
-
-
-
-1.5.3
-Your game shall not be simply ported from a webpage or applying templates. Games with significantly poor user experience will be removed.
-
-
-
-1.5.4 The key features of your game must not rely on third-party applications or require redirecting to the web page to access content and features.
-
-
-
-1.5.5
-The content of your game shall not be the same as any other games that are already accepted on TapTap. You can file a complaint if you find the copyright of your game has been infringed. Click to learn more about the infringement complaint.
-
-
-
-### 1.6 Frauds
-
-
-
-1.6.1
-Games that involve fraud or mislead users will be removed. The developer account may also be suspended.
-
-
-
-1.6.2
-You should keep monitoring the content of your game before and after it passes the review. Games that provide illegal services after the release will be removed. The developer account may also be suspended.
-
-
-
-### 1.7 Games that will not accepted
-
-
-
-1.7.1
-Games that are pirated, fail to obtain authorization from the copyright owner or infringes the copyright of third parties (including but not limited to images, music and text assets, etc.)
-
-
-
-1.7.2
-Games involving gambling, casinos or lottery (including but not limited to cards, fishing and crane machines, etc.), or games promoting gambling, such as games using poker, roulette, or blackjack; or games including props based on real chips as in-app purchases.
-
-
-
-1.7.3 Games involving digital currencies, bitcoin or blockchains.
-
-
-
-1.7.4 Games that contain sexual, violent, blood and gore content.
-
-
-
-1.7.5 Games that promote cults and feudal superstitions, incite hatred and disparage an individual or group.
-
-
----
-
-
-
-## 2. Information Requirements
-
-
-
-### 2.1 Providers
-
-
-
-2.1.1 The title of the provider can only contain numbers, letters, and their combinations; it cannot contain any punctuations or symbols.
-
-
-
-2.1.2 The title of the provider shall not use placeholder text, spaces, garbled text and other meaningless characters.
-
-
-
-2.1.3 The provider shall not be named after the title of any game.
-
-
-
-2.1.4 The provider shall not be named after any individual.
-
-
-
-2.1.5 The title of the provider cannot be the combination or list of multiple providers' titles.
-
-
-
-### 2.2 Genre, Compatibility and Payment
-
-
-
-2.2.1 The information needs to be filled in according to the actual situation of your game. The genre must match the actual features of your product.
-
-
-
-2.2.2 Options need to be selected according to the actual situation and must not be filled in incorrectly to mislead players.
-
-
-
-2.2.3 The payment information of the game should be filled in according to the actual situation and should be properly updated.
-
-
-
----
-
-
-
-## 3. Review of Assets
-
-
-
-### 3.1 Specifications
-
-
-
-3.1.1
-All visual materials such as images and videos must be clear with no obvious blurring, stretching, compression, black edges, white edges, etc. Visual materials cannot be made up of overly simple patterns such as solid colors and gradients.
-
-
-
-3.1.2 The visual materials must not violate any laws and regulations.
-
-
-
-3.1.3
-The visual materials must not contain content that violates the platform's review rules, including but not limited to cash withdrawals, tangible rewards information and related information.
-
-
-
-3.1.4
-The visual materials must not contain information that is not related to the content of the game, including but not limited to advertisements of other games or applications, or contact of the provider.
-
-
-
-3.1.5
-All visual materials must not contain any infringing content. Licensed or purchased assets must be uploaded with corresponding supporting documents or authorization letters.
-
-
-
-3.1.6 All visual materials must not contain statistics such as game store ratings.
-
-
-
-3.1.7 All visual materials must not contain information that is not related to the game.
-
-
-
-3.1.8 All visual materials must not contain information that will redirect users to other websites.
-
-
-
-
-
-### 3.2 Icons
-
-
-
-3.2.1 Icons shall not contain information or corner labels that mislead users or violate relevant regulations. And they shall not contain popular search terms or information unrelated to the content of the game, such as earning cash by playing.
-
-
-
-3.2.2 Icons must be consistent with the content of the game and must not contain popular search terms or information that is not related to the game.
-
-
-
-### 3.3 Screenshots
-
-
-
-3.3.1 The screenshots must not include UI of the device or UI that does not belong to the version provided for download.
-
-
-
-3.3.2 The screenshots need to be of the same size.
-
-
-
-3.3.3 Do not use repeated screenshots as the promotion image for the game detail page on TapTap.
-
-
-
-3.3.4 Screenshots must not contain information that mislead users or violate relevant regulations.
-
-
-
-3.3.5 The screenshots must be consistent with the actual content of the game and must not use assets that are not related to the game for publicity and promotion.
-
-
-
-### 3.4 Cover Image on Game Page and Other Promotion Images
-
-
-
-The above are hereafter collectively referred to as ‘Promotion Image’
-
-
-
-3.4.1 Promotion Images cannot contain any text that is unrelated to the game, and cannot contain any content such as individual contact or UI of the game;
-
-
-
- 3.4.2 Screenshots of the game cannot be used as the Promotion Image. Please do not use collages or tiled images, either.
-
-
-
-3.4.3 Promotion Image must contain game logos (except for the square Promotion Image). The majority of the background should not be left blank.
-
-
-
-3.4.4 The Promotion Image must not contain images of actual mobile phones.
-
-
-
-3.4.5 The Promotion Image must not contain icons of the game.
-
-
-
-3.4.6 Promotion Image cannot be model sheets, drafts or modeling drafts.
-
-
-
-### 3.5 Text Materials That You Provide
-
-
-
-3.5.1 All text materials must not violate any laws and regulations.
-
-
-
-3.5.2 The text must not contain any content that violates the platform's review rules.
-
-
-
-3.5.3 The text must not contain information that redirects users to other third-party websites
-
-
-
-3.5.4 The text must not contain information that promotes conflicts and hatred.
-
-
-
-
-### 3.6 Title of the Game
-
-
-
-3.6.1 The title of the game must be consistent with its registered information, and it shall not include popular search keywords as the subtitle.
-
-
-
-3.6.2 The title of the game shall not contain information that violates the platform’s rules.
-
-
-
-3.6.3 The title of the game shall not contain information that misleads users or is unrelated to the content of the game.
-
-
-
-3.6.4 The title of the game shall not contain information about regions.
-
-
-
-3.6.5 The title of the game displayed after it is installed on the phone should be the same as the title displayed on the game page.
-
-
-
-### 3.7 About and Notes
-
-
-
-3.7.1 The About section on the game page should introduce the features or gameplay. Do not include irrelevant information such as advertisements.
-
-
-
-3.7.2 Content of About and Notes cannot be similar.
-
-
-
-### 3.8 Players’ Workshop
-
-
-
-3.8.1 Player’s workshop is a group for players to communicate, such as a Discord channel. The title should match the game or the official title given for such a group.
-
-
-
-3.8.2 The title of the players' workshop must not contain information that leads or redirects to other application distribution platforms.
-
-
-
----
-
-
-
-## 4. Store Settings
-
-
-
-### 4.1 Status
-
-
-
-4.1.1 The game state must be filled in according to the actual situation
-
-
-
-4.1.2 For games whose test server is not open, the status cannot be ‘Pre-registration’ or ‘Open for Download’.
-
-
-
-### 4.2 Language Settings
-
-
-
-4.2.1 The information in Languages Settings should be consistent with the regions where the game is to be released.
-
-
-
-4.2.2 For each language, the visual and text materials filled in the settings must match with the language.
-
-
-
-
-### 4.3 Links to Official Websites
-
-
-
-The link must be the game's official website; it cannot be a link to any third-party website.
-
-
-
----
-
-
-
-## 5. Application Package for Installation
-
-
-
-### 5.1 Package Name
-
-
-
-5.1.1 The package name must not include any markings of platforms.
-
-
-
-5.1.2 The package name must be consistent with the official package name
-
-
-
-5.1.3 For any changes to the package name, you will need to publish a related announcement and upload the new package.
-
-
-
-5.1.4 Do not use the default package name set by the packaging tool.
-
-
-
-### 5.2 Version Code
-
-
-
-5.2.1 The version code cannot be 0.
-
-
-
-5.2.2 The version code of the newly uploaded installation package must be earlier than the current one.
-
-
-
-### 5.3 Version Name
-
-
-
-The version name must be consistent with the one on the game's official website.
-
-
-
-### 5.4 Update Notes
-
-
-
-The content of the update note must be related to the changes to the game. It shall not include advertisements or other irrelevant information.
-
-
-
-### 5.5 Signature
-
-
-
-APK must not be signed with a public certificate
-
-
-
----
-
-
-
-## 6. Qualification and License Documents
-
-
-
-### 6.1 Operation and IP Licensing
-
-
-
-6.1.1 A complete and valid proof of the license chain is required if the content of the game is operated by another agency.
-
-
-
-6.1.2 A complete and valid proof of the license chain is required if the assets, storyline, music and other content of the game involve another IP.
-
-
-
- 6.1.3 A true and valid proof is required if the IP-related rights are owned by the company that uploads the game.
-
-
-
-6.1.4 The applied scope of the IP license must match the content of the uploaded game and it must be true and valid.
-
-
-
-### 6.2 Assets Licensing
-
-
-
-6.2.1 Assets in the game shall not use copyright-sensitive content, including but not limited to fan-made materials, materials related to a real individual, or in-game assets of other games.
-
-
-
-6.2.2 The authorization letter for the use of portrait is required if a character of real individual appears in the game.
-
-
-
-6.2.3 A true and valid proof is required if assets in the game are original.
-
-
-
-6.2.4 The proof of order is required if the assets in the game are purchased.
-
-
-
----
-
-
-
-## 7. Others
-
-
-
-7.1 TapTap reserves the right to interpret the Specifications and Rules to the extent allowed by law.
-
-
-
-7.2 TapTap Game Review Specifications and Rules takes effect as soon as it is published.
-
-TapTap reserves the right to make changes to this document at any time and to post the updated document on the website of TapTap Developer Center.
diff --git a/.ci/hk/en/store/store-auth.mdx b/.ci/hk/en/store/store-auth.mdx
deleted file mode 100644
index d59ed8c5a..000000000
--- a/.ci/hk/en/store/store-auth.mdx
+++ /dev/null
@@ -1,63 +0,0 @@
----
-title: Developer Verification
-sidebar_position: 35
----
-
-import { Blue } from "/src/docComponents/doc";
-
-## What is the developer verification?
-
-Verified developers in TapTap will have a "√" in the corner of their profile picture. The verified title will appear on the user homepage too.
-
-The title will be usually displayed as ‘Provider title/Game title + Official/a specific role in the team’.
-
-![](/img/DC-v2/developer-verification-V2.png)
-
----
-
-## How can I be verified as a developer?
-
-You can contact us via **Developer Center** >> **Support** . You can also send your request to our team at [international_operation@taptap.com](mailto:international_operation@taptap.com).
-
-**Template**
-
-> **Subject**
->
-> TapTap Developer Verification - XXX (Provider/Game title)
-
----
-
-> TapTap ID:
->
-> Title to be verified:
->
-> You can apply for more than one account to be verified as a developer. Please provide the list in .xlsx, .txt or other formats.
->
-> TapTap ID and the title to be verified:
->
-> Contact of the applicant:
-
-Our staff will process your application in 2 business days.
-
----
-
-## How do I remove the developer verification?
-
-You can contact us via **Developer Center** >> **Support** . You can also send your request to [international_operation@taptap.com](mailto:international_operation@taptap.com).
-
-**Template**
-
-> **Subject**
-> Remove TapTap Developer Verification - XXX (Provider/Game title)
-
----
-
-> TapTap ID:
->
-> The title to be removed:
->
-> You can apply to remove the developer verification of more than one user. Please provide the list of TapTap ID and titles to be removed in .xlsx, .txt or other formats.
->
-> Contact of the applicant:
-
-Our staff will process your application in 2 business days.
diff --git a/.ci/hk/en/store/store-complaint.mdx b/.ci/hk/en/store/store-complaint.mdx
deleted file mode 100644
index 75f0de548..000000000
--- a/.ci/hk/en/store/store-complaint.mdx
+++ /dev/null
@@ -1,158 +0,0 @@
----
-title: Report Copyright Infringement
-sidebar_label: Report Infringement
-sidebar_position: 65
----
-import {Red, Blue, Black, Gray} from '/src/docComponents/doc';
-
-
-TapTap is a platform for downloads and purchases of games, and it is also a high-quality community for game enthusiasts. TapTap has formulated these guidelines according to the relevant laws and regulations. Please make the complaint referring to the following guidelines for the protection of the legitimate rights and interests of the right holder.
-
-## **1. Procedures**
-**1.1 Inform TapTap**
-If the right holder believes that the game provided by the third party on TapTap infringes his/her legitimate rights and interests, the right holder shall submit a written complaint to TapTap, the content should include but not be limited to:
-
-**(1) Information and related materials about the right holder**
-Rights holder’s name (title), contact information, address, business license (for organizations) or ID card (for individuals), relevant authorization certificate and other materials to prove the legitimacy of the right holder.
-
-**(2) The request of the right holder**
-The exact and accurate name and link of the game that the right holder requests to remove or disconnect.
-
-**(3) Preliminary materials that prove the infringement**
-These materials should include:
-a. Proof of right holder's ownership to the rights, which includes but is not limited to the copyright certificate, trademark certificate, or patent certificate issued by the relevant authority, the proof of the date of first public publication or release of the work, the manuscript or drafts during the creation, the time stamp of the creation of the work issued by the authority, the certificate of filing the work and other valid proof of ownership that proves that the right holder indeed owns the relevant rights.
-
-b. Proof that the game provided by the respondent constitutes infringement, which includes but is not limited to the valid proof that the game provided by the respondent constitutes infringement of the copyright, trademark right, patent right, or other rights of the right holder.
-
-**(2) Statement by the right holder**
-The notice letter from the right holder must include the following statement.
-The statements and materials provided by the right holder in the complaint are true, valid and legal; and the right holder guarantees to bear and compensate for any loss caused to TapTap as a result of the removal or modification of the infringing information or related content by TapTap based on the right holder's complaint, including but not limited to the damages incurred by TapTap as a result of compensation to the respondent or the users and the damage to TapTap's reputation, etc.
-
-Please refer to the instructions below for the matters needing attention when preparing the notice, the related certification materials and the mailing address.
-
-**1.2 Feedback from TapTap**
-As a neutral platform service provider, TapTap will forward it to the respondent after receiving the notice from the right holder that complies with the requirements of these guidelines.
-
-**(1) The respondent accepts the complaint from the right holder.**
-TapTap will process this as soon as possible in accordance with relevant laws and regulations.
-
-**(2) The respondent does not accept the complaint from the right holder.**
-TapTap will forward the relevant materials provided by the respondent to the right holder. If the right holder disagrees with the respondent's opinion or the provided materials, TapTap suggests that the right holder resolve the relevant issues directly with the respondent through administrative complaints, litigation or other means. The right holder may also provide them to TapTap if the right holder has new supporting materials that can disprove the respondent's opinion
-
-## **2. Attention**
-(1) The right holder in this document refers to the original owner or the agent that is legally authorized by the original owner of the legal rights of the protected copyright, trademark, patent, etc. This includes legal entities, individuals or other organizations.
-
-(2) In order to ensure the authenticity and validity of the complaint, the written notice of the right holder and other relevant supporting materials should, in principle, be provided in the original. If the original is not available, a copy should be provided (with the signature of the right holder on the copy). If the materials are foreign, they should be notarized and transferred in accordance with the relevant laws, and the corresponding documents about the notarization and transfer should be provided at the same time.
-
-(3) Contact TapTap: Send an email to [dmca@taptap.com](mailto:dmca@taptap.com) with the scanned copies of all hard copies of the aforementioned materials as attachments.
-
-(4) The written notice by the right holder in this document should include the notice itself and relevant entity qualification materials, proof of ownership, proof of infringement and other materials.
-
-(5) If the right holder has filed an administrative complaint or lawsuit with the relevant government department or court concerning the game provided by the respondent, he/she should submit the relevant certificate of acceptance and the evidential materials submitted to the government department or court to TapTap when submitting the notice. This will facilitate the processing of the right holder's complaint.
-
-(The End)
-
-## **Download [Infringement Complaint Template](https://lc-gluttony.s3.amazonaws.com/vmzp7NxP3swl/Vm0BDugjlYJQQ5veMyKm0YdzEcm6RdAv/TapTap%E5%B9%B3%E5%8F%B0%E4%BE%B5%E6%9D%83%E6%8A%95%E8%AF%89%E9%80%9A%E7%9F%A5%E4%B9%A6.doc.zip)**
-
----
-
-# **Response Letter to Infringement Complaint**
-
-## **1. Response Letter to TapTap by Respondent**
-
-The Respondent shall submit a written response letter to TapTap within five business days after receiving the relevant complaint submitted by the right holder and forwarded by TapTap. The response letter shall be in accordance with the following guidelines, which shall include, but not be limited to, the following content.
-
-**1.1 Information and related materials about the respondent**
-The respondent’s name (title), contact information, address, business license (for organizations) or ID card (for individuals), relevant authorization certificate and other materials to prove the entity qualification of the respondent.
-
-**1.2 Respondent’s response to the complaint**
-a. Admitting to the infringement.
-b. Not admitting to the infringement.
-
-**1.3 Preliminary materials that disprove the infringement**
-When not admitting to the infringement, the respondent shall provide materials that disprove the infringement, which should include the following.
-(1) Proof of respondent’s ownership to the rights, which includes but is not limited to the copyright certificate, trademark certificate, or patent certificate issued by the relevant authority, the proof of the date of first public publication or release of the work, the manuscript or drafts during the creation, the time stamp of the creation of the work issued by the authority, the certificate of filing the work and other valid proof of ownership that proves that the respondent indeed owns the relevant rights.
-
-(2) Proof that the game provided by the respondent does not constitute infringement, which includes but is not limited to the valid proof that the game provided by the respondent does not constitute infringement of rights, such as the copyright, trademark right, or patent right.
-
-**1.4 Statement by the respondent**
-The respondent shall make the following statement in the response letter.
-The statements and materials provided by the respondent in the response letter are true, valid and legal; and the respondent guarantees to bear and compensate for any loss caused to TapTap as a result of the removal or modification of the infringing information or related content by TapTap based on the respondent’s response letter, including but not limited to the damages incurred by TapTap as a result of compensation to the right holder or the users and the damage to TapTap's reputation, etc.
-Please refer to the instructions below for the matters needing attention when preparing the response letter, the related certification materials and the mailing address.
-
-## **2. Feedback from TapTap**
-As a neutral platform service provider, TapTap will process the matter according to the relevant laws and regulations.
-
-**2.1 The respondent accepts the complaint from the right holder.**
-TapTap will resolve the matter as soon as possible in accordance with relevant laws and regulations.
-
-**2.2 The respondent does not accept the complaint from the right holder.**
-TapTap will forward the materials provided by the respondent to the right holder. TapTap will resolve the matter in accordance with relevant laws and regulations.
-
-## **3. Attention**
-3.1 The right holder in this document refers to the original owner or the party that is legally authorized by the original owner of the legal rights of the protected copyright, trademark, patent, etc. This includes legal entities, individuals or other organizations.
-
-3.2 In order to ensure the authenticity and validity of the materials provided by the respondent, the written response letter of the respondent and other relevant supporting materials should, in principle, be provided in the original. If the original is not available, a copy should be provided (with the signature of the respondent on the copy). If the materials are foreign, they should be notarized and transferred in accordance with the relevant laws, and the corresponding documents about the notarization and transfer should be provided at the same time.
-
-3.3 Contact TapTap: Send an email to [dmca@taptap.com](mailto:dmca@taptap.com) with the scanned copies of all hard copies of the aforementioned materials as attachments.
-
-3.4 The written response letter by the respondent in this document should include the response letter itself and entity qualification materials, proof of ownership, proof that disproves infringement and other materials.
-
-(The End)
-
-## **Download [Response Letter to Complaint Template](https://lc-gluttony.s3.amazonaws.com/vmzp7NxP3swl/3hyv6Bn92qHDTsnetWTqhg8sHx2zcJCb/TapTap%E5%B9%B3%E5%8F%B0%E4%BE%B5%E6%9D%83%E6%8A%95%E8%AF%89%E5%8F%8D%E9%80%9A%E7%9F%A5%E4%B9%A6.doc.zip)**
-
----
-
-# Complaints to Reviews and Community Content
-
-TapTap is a platform for downloads and purchases of games, and it is also a high-quality community for game enthusiasts. TapTap has formulated these guidelines according to the relevant laws and regulations. Please make the complaint referring to the following guidelines for the protection of the legitimate rights and interests of the right holder.
-
-## **1. Procedures**
-**1.1 Inform TapTap**
-If the right holder believes that the information published by the third party on TapTap infringes his/her legitimate rights and interests, the right holder shall submit a written complaint to TapTap, the content should include but not be limited to:
-
-**(1) Information and related materials about the right holder**
-Rights holder’s name (title), contact information, address, business license (for organizations) or ID card (for individuals), relevant authorization certificate and other materials to prove the legitimacy of the right holder.
-
-**(2) The request of the right holder**
-Link to the information that the right holder requests to delete or modify.
-
-**(3) Preliminary materials that prove the infringement**
-These materials should include:
-a. Proof of right holder's ownership to the rights, which includes but is not limited to the copyright certificate, trademark certificate, or patent certificate issued by the relevant authority, the proof of the date of first public publication or release of the work, the manuscript or drafts during the creation, the time stamp of the creation of the work issued by the authority, the certificate of filing the work and other valid proof of ownership that proves that the right holder indeed owns the relevant rights.
-
-b. Proof that the game provided by the respondent constitutes infringement which includes but is not limited to the valid materials which can prove that the information published by the respondent constitutes infringement of the legitimate rights and interests of the right holder, etc.
-
-**(2) Statement by the right holder**
-The notice letter from the right holder must include the following statement.
-The statements and materials provided by the right holder in the complaint are true, valid and legal; and the right holder guarantees to bear and compensate for any loss caused to TapTap as a result of the removal or modification of the infringing information or related content by TapTap based on the right holder's complaint, including but not limited to the damages incurred by TapTap as a result of compensation to the respondent or the users and the damage to TapTap's reputation, etc.
-
-Please refer to the instructions below for the matters needing attention when filing the complaint, the related certification materials and the mailing address.
-
-**1.2 Feedback from TapTap**
-After receiving the complaint from a rights holder that meets the requirements of the guidelines, TapTap will determine the type of complaint.
-
-**(1) If the complaint by the right hold can be decided inside TapTap**
-TapTap will resolve the matter as soon as possible in accordance with relevant laws and regulations.
-
-**(2) If the complaint by the right hold cannot be decided inside TapTap**
-TapTap suggests that the right holder resolve the issue directly with the respondent by administrative complaints or lawsuits, etc.
-
-TapTap will then process the rights holder's complaint as soon as possible and in accordance with the TapTap Community Management Rules and other relevant regulations.
-
-## **2. Attention**
-(1) The right holder in this document refers to the original owner or the agent that is legally authorized by the original owner of the legal rights of the protected copyright, trademark, patent, etc. This includes legal entities, individuals or other organizations.
-
-(2) In order to ensure the authenticity and validity of the complaint, the written notice of the right holder and other relevant supporting materials should, in principle, be provided in the original. If the original is not available, a copy should be provided (with the signature of the right holder on the copy). If the materials are foreign, they should be notarized and transferred in accordance with the relevant laws, and the corresponding documents about the notarization and transfer should be provided at the same time.
-
-(3) Contact TapTap: Send an email to [dmca@taptap.com](mailto:dmca@taptap.com) with the scanned copies of all hard copies of the aforementioned materials as attachments.
-
-(4) The written complaint by the right holder in this document should include the complaint itself and relevant entity qualification materials, proof of ownership, proof of infringement and other materials.
-
-(5) If the right holder has filed an administrative complaint or lawsuit with the relevant government department or court concerning the information published by the respondent, he/she should submit the relevant certificate of acceptance and the evidential materials submitted to the government department or court to TapTap when submitting the complaint. This will facilitate the processing of the right holder's complaint.
-
-(The End)
-
-## **Download [Community Complaint Template](https://lc-gluttony.s3.amazonaws.com/vmzp7NxP3swl/gBKS91vj66v2crFLJ7w7xutBtF6j7zAF/TapTap%E5%B9%B3%E5%8F%B0%E8%AF%84%E4%BB%B7%E5%8F%8A%E7%A4%BE%E5%8C%BA%E6%8A%95%E8%AF%89%E4%B9%A6.doc.zip)**
-
diff --git a/.ci/hk/en/store/store-contact.mdx b/.ci/hk/en/store/store-contact.mdx
deleted file mode 100644
index e4d9a1998..000000000
--- a/.ci/hk/en/store/store-contact.mdx
+++ /dev/null
@@ -1,11 +0,0 @@
----
-title: Contact Us
-sidebar_position: 70
----
-
-Feel free to contact us at the following email addresses if you have any questions.
-
->Questions about Developer Center:
-
->Business Opportunities:
-
diff --git a/.ci/hk/en/store/store-creategame.mdx b/.ci/hk/en/store/store-creategame.mdx
deleted file mode 100644
index ccf55faeb..000000000
--- a/.ci/hk/en/store/store-creategame.mdx
+++ /dev/null
@@ -1,162 +0,0 @@
----
-title: Create And Release Games
-sidebar_position: 45
----
-
-import { Blue } from "/src/docComponents/doc";
-
-Once your created TapTap developer account has been approved, you can create and publish your games in the Developer Center.
-
-## Create Game
-
-Go to **[Developer Center](https://developer.taptap.io/)** >> Click on **Create Game** on the left >> Fill in the required information of the game to create the game.
-
-- When creating a game page, the system will automatically generate a version name (for example: V-20220818) based ont the date created. The version name will be used to track the review status of this version.
-- After the game is created for the first time, the status of the game will be 'Not Released'. To **release the game**, you will need to submit it for review.
-- You can only create up to 10 games that are to be released.
-- To delete a game that you have created, you may go to All Games and find ![‘deleteadmin’](https://img.tapimg.com/market/images/2e5c836549d866d6d44036d158095cbb.png) in the right
-
-![](/img/DC-v2/create-V2en-1.png)
-
-Example
-
-![](/img/DC-v2/create-V2en-2.png)
-
-After the game is created, you can view all created games in **All Games** page, and click ont the game title to view details.
-
-![](/img/DC-v2/create-V2en-3.png)
-
-## Release Game
-
-Go to game >> Fill in ** Store Information** >> Submit it for review. After approved, the game page will be av ailable to players on TapTap.
-
-### Select Region And Status
-
-If you need to specify the regions for the release, you can add the region in the game's **Region and Status** section, and your game will only be available for download or pre-registration in the selected regions.
-
-For unlisted regions, the page will show the basic information of the game such as the title, the provider and the banner on top. Playes will only be able to follow the game.
-
-![](/img/DC-v2/create-V2en-4.png)
-
-TapTap Interntional does not have a strict concept of regions. We want to create an app for users from all over the world to come and enjoy the games. When you are promoting your game, we recommend you to make your game available for pre-registration globally so it has more exposure and popularity.
-
-### Set Up Language Options
-
-You can add language options in **Store Info** >> **Game Assets**, so your game can be enjoyed by a wider audience.
-
-- English is required if you are going to release your game globally.
-- If your game is going to be released to a specific country、region, it is recommeded that you add English and the official language of the region.
-
-For example, if the game is released to Korea, you can add 'English' and 'Korean' assets.
-
-Languages currently supported are: Traditional Chinese, English, Japanese, Korean, Indonesian, Thai, Portuguese, Vietnamese, Hindi, Malay, Spanish, French, German, Russian.
-
-You can add multilingual assets according to your needs.
-
-![](/img/DC-v2/create-V2en-5.png)
-
-Add/Remove Languages
-
-![](/img/DC-v2/create-V2en-6.png)
-
-### Set the APK
-
-The APK is required when the game is under test and open for download. You can go to **Developer Center** >> **Store Info** >> **APK** to upload.
-
-In order for players from all over the world to have a better experience after downloading the game, please upload the APK and fill in the **languages supported** according to the actual stuation. This will be displayed on the game page.
-
-![](/img/DC-v2/create-V2en-7.png)
-
-Package Formats
-
-- Developers can only upload packages in APK.
-- If you need to upload APK+OBB, you can contact us from **Developer Center** >> **Support**, or send your request to [international_operation@taptap.com](mailto:international_operation@taptap.com). Our team will assist you to upload the packages.
-
-### Schedule Release Time
-
-When the game page information is complete, you can select **release options** in the **Release Settings**.
-
-![](/img/DC-v2/create-V2en-8.png)
-
-- Select **Release Immediately** if you want the game page to be displayed on TapTap as soon as the game has been approved.
-- If you want to release the game page in TapTap at a fixed time, you can select **Scheduled** and set up the specific date and time for release in schedule.
-
-**Please note that you will need to set the time to at least 24 hours after the current time.**
-
-### Edit Schedule
-
-Refer to the following method if you need to change the schedule for release.
-
-- The Version has been approved
- - Click on **Edit Schedule** to re-select the release time of the game page. You will only be able to select after the current time.
- - You can also **Cancel Schedule**, and the status of this version will change to **Rejected**. You will need to set the new schedule and submit again for review.
-- The version is under review
- - Select **Cancel Review** and you will need to submit the version for review again after creating a schedule.
-
-## Submit For Review
-
-Once the game page information is complete, you can proceed to **Submit For Review**. The result is expected to be available within 2 business days.
-
-## Q&A
-
-### My game is already on TapTap. Can I claim it?
-
-Yes, of course.
-
-You can contact us via **Developer Center** >> **Support**. You can also send your request to our team at [international_operation@taptap.com](mailto:international_operation@taptap.com) and provide a scanned copy of the business license or other materials that can prove that the game is owned by you or your organization.
-
-**Template**
-
-> **Email subject:** Claim Game On TapTap - Game Title
->
-> Title and link of the game to be claimed:
->
-> Title of the provider making the request:
->
-> Scanned copy of business license or other proof materials (you can also upload as attachments):
->
-> Contact of the applicant:
-
-our operation team will process your request in 2 business days.
-
-### Can the provider of the game be changed?
-
-Yes.
-
-You can contact us via **Developer Center** >> **Support**. You can also send your request to our team at [international_operation@taptap.com](mailto:international_operation@taptap.com) and cc this to the other provider's email address as well.
-
-**Template**
-
-> **Email subject:** Change Provider on TapTap - Game Title
->
-> Link to the game page on TapTap:
->
-> Title of the original provider of the game:
->
-> Title of the new provider of the game:
->
-> Contact of the applicant:
-
-Please note that this email needs to be sent with the provider's company email and cc to the other provider's company email as well. If you are an individual developer, the email addresses of the original provider and the new provider must be the email addresses filled in the TapTap provider profile. The vendor who received the copied email needs to reply for confirmation.
-
-After receiving the confirmation email, our operation team will verify and process your request in 2 business days.
-
-### How do I take down the game from TapTap?
-
-You can contact us via **Developer Center** >> **Support**. You can also send your request to our team at [international_operation@taptap.com](mailto:international_operation@taptap.com) and provide a scanned copy of the business license or other materials that can prove that you are an employedd of the provider.
-
-**Template**
-
-> **Email subject:** Remove Game from TapTap - Game Title
->
-> Link of the game to be removed:
->
-> Reason for removal:
->
-> Scanned copy of business license or other proof materials (you can also upload as attachments):
->
-> Applicant Contact:
-
-Our operation team will process your request in 2 business days.
-
-**We greatly value the content created by our users. When your game is removed from TapTap, we will still keep your game's page to show users' reviews and posts but it will not be available for download anymore. If you need to remove the game page as well, please specify in your email.**
diff --git a/.ci/hk/en/store/store-devagreement.mdx b/.ci/hk/en/store/store-devagreement.mdx
deleted file mode 100644
index ca6bdb51b..000000000
--- a/.ci/hk/en/store/store-devagreement.mdx
+++ /dev/null
@@ -1,266 +0,0 @@
----
-title: TapTap Developer Agreement
-sidebar_position: 15
----
-
-# TapTap Developer Agreement
-
-**Release date: December 29, 2021**
-
-**Effective date: December 29, 2021**
-
-Welcome to TapTap Developer Center!
-
-To use the service provided by TapTap Platform to Developers (“Service”), you shall read and comply with this TapTap Developer Agreement (“Agreement”).
-
-**Please carefully read and fully understand the terms and conditions of this Agreement, especially those on waiver or restriction of liabilities, and separate agreements and rules on provision or use of a certain service, which may be written in bold.**
-
-**Unless you have read and accepted all the terms and conditions of this Agreement, relevant agreements and rules, etc., you have no right to use the Service. If you use the Service, it will be deemed that you have read and agreed to comply with such agreements and rules.**
-
-**If you have breached this Agreement, TapTap has the right to restrict, suspend or terminate the Service provided to you at any time, and hold you accountable for relevant liabilities.**
-
-**TapTap may update or amend the content, including but not limited to the terms and conditions, of this Agreement, the agreements and rules related to the Service from time to time. Any such update and/or amendment, once published, shall be integral to this Agreement, and you are expected to understand and comply with the same.**
-
-## 1. Accepting this Agreement
-
-1.1 This Agreement is a legal instrument between you and TapTap on Game release, publicity and promotion. "TapTap" refers to TapTap Pte. Ltd. and its affiliates, which have the right to operate the TapTap Platform in the relevant area. You agree that once you have accepted this Agreement and registered a Developer Account, Games will be displayed on the TapTap Platform only in your name (not in the name of TapTap) for Users to download (on a paid or free-of-charge basis). **You acknowledge and warrant that you have full civil capacity in your jurisdiction and accept this Agreement; you have the legal qualifications or have been approved by relevant authorities for using the Service, accessing and providing Games or related services; the qualifications or certificates and any other documents provided by you are true, correct and complete, and will be updated promptly if there is any change to such information; you have the capacity to perform the obligations and act under this Agreement; such performance and acts will not be in violation of any legal document that is binding upon you.** **Otherwise, you shall not use the Service provided by the TapTap Platform, and you shall bear all responsibilities and losses caused thereby to Users and TapTap.** If you refuse to accept this Agreement, you shall stop using the Service.
-
-1.2 You shall use the Service provided by the TapTap Platform with a Developer Account which cannot be changed, transferred, gifted or inherited. You shall keep in good custody and make proper use of the account, and be fully liable for the actions under the account.
-
-1.3 If you agree to comply with this Agreement on behalf of your employer or other entity, it shall be deemed that you declare and warrant that you have obtained all legal authorization to subject your employer or such entity to this Agreement. If you have not obtained such authorization, you shall not accept this Agreement or use the TapTap Platform on behalf of your employer or such entity.
-
-## 2. Definitions
-
-2.1 **Brand Marking**: a Game name, trademark, logo, domain name or other distinctive brand feature independently owned (or whose legal authorization is obtained) by either party hereto.
-
-2.2 **Developer or you**: any individual, legal person or organization that has successfully registered with the TapTap Platform and has obtained a Developer Account according to this Agreement.
-
-2.3 **Developer Account**: an account obtained by the Developer through registration and assigned to the Developer by the TapTap Platform, which allows the Developer to release a Game on and use other Services provided by the TapTap Platform.
-
-2.4 **Dashboard**: a backend tool used by the Developer with a Developer Account to manage Games and view data by logging in to “Developer Center” on the TapTap Platform via a console or/and other online tool or Service provided by the TapTap Platform.
-
-2.5 **Games or Products**: products developed by the Developer or for which the Developer has been licensed by the owner, and provided through the TapTap Platform to Users for downloading, pre-registering or testing, etc., including without limitation certain mobile Games and Game-related texts, music, pictures, videos, and entertainment applications.
-
-2.6 **Localized Versions:** Any versions of the Games created for specific languages or jurisdictions.
-
-2.7 **Platform Operation Data:** data generated during use of the Service or on the TapTap Platform, including but not limited to the information provided by Users and/or Developers, the data arising from their operation, and all types of transaction data. The ownership of and other rights in or to the platform operation data shall belong to the TapTap Platform and are the trade secrets of the TapTap Platform, except the rights owned by Users or enjoyed by Developers in accordance with the law.
-
-2.8 **TapTap Platform**: TapTap (with domain name including but not limited to [taptap.io](https://www.taptap.io/)) website and client in various forms (including new service forms developed with new technology in the future) used to serve you.
-
-2.9 **Users:** all players who directly or indirectly use the Games released or updated by you on the TapTap Platform.
-
-2.10 **User Data**: user-related data generated by a user on the TapTap Platform, including but not limited to the information provided by the user when using the TapTap Platform, the information obtained by the TapTap Platform with the user's consent, the data arising from the user's operation, and all types of transaction data. Except otherwise set forth in the TapTap [Privacy Policy](https://www.taptap.io/privacy-policy), the ownership of and other rights in or to user data shall belong to the TapTap Platform and are the trade secrets of the TapTap Platform.
-
-## 3. Profile
-
-3.1 TapTap is a platform open to the public on which Developers can publish their Games. To publish a Game on the TapTap Platform, you must obtain and maintain a valid Developer Account.
-
-3.2 If your Games are provided for Users to download on a paid basis, or as required by the TapTap Platform, you shall execute a separate agreement with TapTap to cover Game release.
-
-## 4. Both You and TapTap Agree and Understand:
-
-4.1 **The TapTap Platform is a neutral service provider which only provides Game-related services to Developers, including neutral network service or technical support, e.g. uploading and storage, Game release, pre-registering, testing, and transfer to links, so that Developers can release and promote their Games independently on the platform.**
-
-4.2 **With regard to the Developer’s Games, the Developer shall develop and/or operate them independently, have a legal license and assume all responsibilities for such Games. TapTap will not participate in development and/or operation of any Developer's Games or other activities, nor modify or edit such Games.**
-
-4.3 **Any dispute or liability arising from your Games and services and any consequence caused by your violation of laws, rules or regulations or breach of this Agreement shall be settled, handled or borne by you only. In case of infringement upon TapTap’s or any third party's rights and interests, the Developer shall assume full liability and compensate TapTap for all losses arising therefrom.**
-
-## 5. Pricing and Maintenance
-
-5.1 You have the right to decide the price of your Games on the TapTap Platform by yourself or through negotiation with TapTap, or allow Users to download Games for free; the TapTap Platform will display the Games in your name at the price set by you or as agreed upon.
-
-5.2 You shall provide support for your Games. If there are any defects or performance problems in the Games downloaded and installed from the TapTap Platform, Users will contact you accordingly. **You must be solely responsible for your Games. TapTap will not provide any support or maintenance service for your Games, nor settle all complaints related to your Games. A lack of appropriate information or support provided for your Games may result in low ratings, poor exposure, decreased sales, settlement disputes, etc., and TapTap has the right to remove such Games from the platform.**
-
-5.3 You may authorize Users to reinstall each Game downloaded via the TapTap Platform multiple times, unless the Game is removed by you or TapTap.
-
-## 6. Your Use of the TapTap Platform
-
-6.1 You agree to submit the Games to TapTap for release no later than the first commercial release of each Game or Localized Version, or, if already commercially released before the Effective Date, within thirty (30) days of the Effective Date. Thereafter, you shall submit to TapTap any Localized Versions and updated versions (in beta and final form) when available, but in no event later than they are provided to any other third party for commercial release.
-
-6.2 Except for the licensing rights granted under this Agreement or a separate agreement entered into between you and TapTap, TapTap will not obtain the rights, ownership or interests of Games from you (or your licensor), including the current intellectual property rights in or to such Games.
-
-6.3 You agree to use the TapTap Platform only when permitted by this Agreement and any applicable laws or regulations or generally accepted practices or guidelines in the relevant jurisdiction.
-
-6.4 You agree to protect the privacy and legal rights of Users when using the Service, at least to the degree, in principle, as set forth in the TapTap [Privacy Policy](https://www.taptap.io/privacy-policy). If your protection is obviously inferior to that degree, you shall inform the User and obtain his/her consent. For example, if Users provide their username, password, or other login information or personal information in order to access or use your Games, then you must not only let the Users know that such information will only be used for your Games, but also provide them with a privacy statement and sufficient protections. In addition, your Games can only use such information for the limited purposes permitted by Users. If your Games contain personal or sensitive information provided by Users, it must be stored securely and used or disclosed according to the local laws and regulations and the relevant national or regional standards. However, if Users choose to sign a separate agreement with you, which allows you or your Games to store or use their personal or sensitive information directly related to your Games (excluding other Games), then your use of such information will be subject to the said agreement. If Users provide the information of TapTap accounts or other accounts for your Games, then your Games can only use such information to access such accounts, and only for the limited purposes, as permitted by them. You shall inform Users of the manner to modify or delete User Data, ensure that Users can act in that manner by themselves when they request to delete their User Data, and that such data is completely deleted.
-
-6.5 You agree that you will not engage in or participate in any interference, interruption, destruction or unauthorized access to any third-party equipment, server, network or other property or service when using the TapTap Platform or the Service. This provision also applies to Game development or release. You shall not use any User information obtained from the TapTap Platform to sell or release Games outside of the TapTap Platform, nor provide data obtained from the TapTap Platform in any form to another party, nor sell or transfer customer information. Without the prior written consent of TapTap, you shall not use the relevant data for purposes other than stipulated in this Agreement.
-
-6.6 **You agree that you are solely responsible for the consequences of any Games you release on the TapTap Platform and that use any API or TapSDK; TapTap will not bear any responsibility to you or any third party. These consequences include, but are not limited to, responsibilities, consumer protection and/or intellectual property responsibilities related to your Games.**
-
-6.7 **You agree to take full responsibility for all violations of the obligations as set forth in this Agreement, any applicable third-party contract or service provisions, or any applicable laws or regulations, and the consequences thereof (TapTap shall be held irresponsible to you or any third party). If the Games you release on the TapTap Platform have defects in rights or infringes upon the legal rights of a third party (including without limitation patent rights, trademark rights, copyrights and adjacent copyrights, portrait rights, personal information rights, privacy rights, and reputation rights), which subject the TapTap Platform and its affiliates or the third parties that cooperate with the TapTap Platform (“indemnified parties”) to any claim or suit, or cause the indemnified parties to suffer any loss of reputation or property, you shall take all possible measures to indemnify and hold harmless the indemnified parties against such claims and suits. At the same time, you shall be fully liable for the direct and indirect financial losses borne by the indemnified parties.**
-
-6.8 You agree to classify your Games according to the local laws and regulations, or relevant policies and industry standards. **All legal consequences and losses caused by the improper classification you declare shall be borne by you, and you shall compensate TapTap for all resulting losses.**
-
-6.9 Users may rate and review Games on the TapTap Platform. You agree that you shall not artificially manipulate the user evaluation system for Games. TapTap has the right to determine or change the position of Games displayed on the TapTap Platform at its own discretion, and to display Games and their ratings and reviews to Users in a manner determined by TapTap.
-
-6.10 You are responsible for uploading your Games to the TapTap Platform, providing Users with the necessary information and support, and accurately disclosing the necessary security permissions to ensure the normal operation of your Games on Users’ devices. If your Games are not uploaded normally, they will not be released on the TapTap Platform. Your Games shall meet TapTap's requirements in technology and security to ensure their safety and stable operation on the TapTap Platform. At the same time, in order to provide Users with high-quality Products and services, **you shall continuously update your Games after they are launched and in operation (including without limitation downloading, test, and trial), and ensure that the Game version on the TapTap Platform is the latest version available via various public channels. That means the Game version you provide to the TapTap Platform shall not be lower than that on any other Android Game platform or channel regardless of whether the Game version on other platforms or channels is provided by you or a third party with your authorization**.
-
-6.11 You shall provide relevant holders with an effective complaint channel to ensure that they can claim rights against you when they believe that you have infringed upon their legal rights and interests.
-
-6.12 You shall not bypass, attempt to bypass, or claim to be able to bypass any content protection systems or data analysis tools provided by TapTap, or intentionally mislead Users into making them believe that they are directly interacting with TapTap.
-
-6.13 In order to provide you with more comprehensive and high-quality services, TapTap or its partners ("providers of individual services") may also provide you with other individual services, including without limitation advertising and TapSDK (or API) service. You have the right to decide by yourself whether or not to use such individual services as appropriate.
-
-6.14 For the purpose of this article, individual services may be subject to a separate agreement and/or rules, etc., or their functions, rules and requirements may be provided to you in the form of announcement or reminder, etc. If you use an individual service, you shall follow the requirements to open the service, and observe the said agreement, rules, announcement and/or reminder, etc.
-
-6.15 By accessing or using an individual service in any form, you have understood and agreed to comply with the agreement, rules, announcement and/or reminder, etc.
-
-6.16 You understand and agree that the provider of individual services has the right to operate independently and take the following measures without your consent or giving prior notice:
-
-- Amend the relevant agreement and rules of individual services, including without limitation using conditions, methods, and charges;
-- Change the specific content of an individual service, suspend or terminate an individual service.
-
-If you do not accept the said change or amendment, you shall stop using the relevant service. Otherwise, if you continue to use such service, it shall be deemed that you have agreed and accepted such change or amendment.
-
-6.17 The Games that you publish on the TapTap Platform or for which you use the Services shall be in compliance with the TapTap policies, all applicable laws and other obligations worldwide. You shall declare and guarantee that the Games you release shall not contain the following content or behavior:
-
-- Content in violation of local laws and regulations;
-- Content that is harmful to children, such as pornographic content related to minors, excessive violence and blood, and content that depicts or encourages harmful and dangerous activities;
-- Spreading obscenity, pornography, gambling, violence, murder, terror or abetment;
-- Content that violates public order and good customs and impairs public interest;
-- Content that insults or slanders others or infringes upon the lawful rights and interests of others;
-- Violent intimidation, threat or fraud against others, or conducting human flesh searches;
-- Spreading commercial advertisements, or similar commercial solicitation information, excessive marketing information, and spam;
-- Infringing upon the legal rights of a third party’s privacy, reputation, portrait, intellectual property, etc.;
-- Other content or behaviors that TapTap rejects or deems inappropriate, or are prohibited by applicable laws worldwide.
-
-If you have violated this article, TapTap has the right to issue a warning, impose penalties (fines), restrict/suspend Service, remove Games, close the application portal, or terminate Service, etc. You shall compensate for all losses caused to Users and/or TapTap or any partners and/or affiliates of TapTap due to any violation of any of the above provisions.
-
-## 7. Grant of License
-
-7.1 You grant TapTap a worldwide, non-exclusive license to release Games (official or beta versions) you have uploaded to this platform, provide Users with Game downloading, pre-registering, test services, and/or release Games in the manner as set forth in this Agreement or a separate agreement executed by both parties.
-
-7.2 **For purposes of clarity, unless otherwise agreed upon in writing by you and TapTap, by providing Users with Game pre-registering or testing on the TapTap Platform, you agree to grant TapTap a non-exclusive license, including without limitation allowing TapTap to release the said Game including any subsequent version, providing Users with pre-registering, testing and downloading services for the Game, as well as the non-exclusive license as set forth in Article 7 of this Agreement, and agree to comply with the provisions hereof on relevant rights and obligations during the licensing period, regardless of whether or not you have granted or will grant an exclusive license to a third party other than TapTap to release the Game worldwide or in the designated region during or after pre-registering or testing of the Game provided on the TapTap Platform.**
-
-7.3 You grant TapTap a worldwide, non-exclusive license to copy, operate, display, analyze and use Games in the events related to: (a) operation and marketing of the TapTap Platform; (b) public presentation to introduce, promote, and advertise Games; (c) data sharing within the TapTap Platform; (d) improvement of TapTap services, and (e) verifying compliance with this Agreement and other related platform policies.
-
-7.4 You shall declare and guarantee that you own all intellectual property rights contained in and related to the Games, including all necessary patents, trademarks, trade secrets, copyrights and other proprietary rights. If you use the content provided by a third party, you shall declare and guarantee that you have the right to release such content in the Games and have had authorization from the third party. You agree that you will not submit to the TapTap Platform any content subject to copyright, trade secret or third-party proprietary rights (including patents, privacy rights and publicity rights), unless you are the owner of such rights, or the legal owner of such rights allows you to submit the content.
-
-7.5 **You agree and warrant that after the TapTap Platform allows Game pre-registering, testing, trial play or downloading, you shall continue to update and provide downloading service in order to ensure the interests of Users and user experience. If you fail to provide continuous updating and downloads, it shall be deemed that you authorize TapTap to update on its own, including the right to automatically update the TapTap Platform based on the version you have released in other channels. This provision shall prevail over any agreement executed between you and other channels, and all losses arising therefrom shall be borne by you, and you shall compensate TapTap for all losses incurred.**
-
-## 8. Intellectual Property Rights
-
-8.1 Each party has all its rights, ownership and interests, including but not limited to all intellectual property rights in or to its Brand Marking. Except for the limited scope expressly set forth in this Agreement, neither party shall grant any rights, ownership or interests (including without limitation any implied license) contained in or related to any Brand Marking to the other party; nor shall the other party acquire such rights, ownership or interests. According to this Agreement, the Developer grants TapTap and its affiliates a non-exclusive, limited license during the term of this Agreement to release Games on the TapTap Platform or fulfill their obligations under this Agreement and display the Developer’s Brand Marking submitted by the Developer to the TapTap Platform online or on mobile devices. Nothing in this Agreement grants the Developer the right to use any trade name, trademark, service mark, logo, domain name or other unique Brand Marking of the TapTap Platform.
-
-8.2 The Developer agrees that in order to help TapTap fully display Games, TapTap and its affiliates or a third party in cooperation with the TapTap Platform may also use the Developer’s Brand Marking submitted by the Developer to the TapTap Platform for: (a) any online or mobile Game/service owned by the TapTap Platform; (b) network, mobile, TV, outdoor (e.g. billboards) and printed advertisements other than the TapTap Platform (when the Games are mentioned together with the TapTap Platform or other Games on the platform) ; (c) Game release announcement; (d) presentation; and (e) customer list displayed online or on mobile devices (including but not limited to the customer list published on the TapTap Platform).
-
-8.3 **In order to promote Games, you authorize Users to release pictures or videos containing your Game’s content on the TapTap Platform. The content authorized to be released by Users shall not be in violation of local laws and regulations and the specifications of the TapTap Platform. You have the right to handle Users’ violations by yourself or make a complaint by methods defined by the TapTap Platform.**
-
-## 9. Game Removal
-
-9.1 Game removal by the Developer. Unless otherwise agreed upon in writing between you and TapTap, you may apply to terminate release of Games to Users on the TapTap Platform (“removal”) if: (a) you decide to terminate operation of the Games; and (b) you have removed the Games from all other Android platforms or channels, or ensure that the Games are not removed from such other platforms or channels later than from the TapTap Platform. You shall notify the TapTap Platform of your decision in writing 90 days earlier, stating the reason for removal, the plan for or the status of such Games on other platforms or channels, and the date such Games are to be removed from the TapTap Platform. The Games can be removed after confirmation by TapTap and fulfillment of the relevant procedures.
-
-Regardless of the reason, if you need to terminate your Games, you shall post an announcement of termination to Users on the Game-related page at least 60 days in advance according to the relevant regulations, and close the payment portal. The announcement shall be posted until the day of such termination.
-
-The removal of the Game: (a) will not affect the license of Users who have purchased or downloaded the Games; (b) will not result in any removal of such Games from the devices of Users who have acquired the Games; and (c) will not change your obligations of distribution or support to Users who have purchased or downloaded Games or services.
-
-9.2 Game removal by TapTap Platform. Although TapTap is not obliged to monitor the content of Games, if via your notifications, third-party complaints, User complaints, institutional supervision or other means, TapTap learns that your Games, including any part of them, or your Brand Marking: (a) infringe upon the intellectual property rights or any other rights of a third party; (b) are in violation of any applicable law or prohibition order; (c) are released improperly by you; (d) may subject TapTap or its authorized operators to legal liabilities; (e) are deemed by TapTap to carry a virus, or as malware or spyware, or to have adverse effects on TapTap or its authorized operators’ network; (f) are in violation of this Agreement or other agreements, terms or rules intended for the Developer; or (g) are displayed with effects on the integrity of the TapTap Platform server (that is to say Users cannot access the TapTap Platform, the Games, or encounter other difficulties in accessing the foregoing content), then TapTap may decide at its own discretion to remove the Game from the TapTap Platform, or re-classify and re-rate the Games.
-
-If you have violated any agreement reached with, or any rule disclosed by, the TapTap Platform, TapTap may restrict, suspend or terminate any Service provided to you, including removal of your Games, according to relevant agreements and rules. TapTap reserves the right to restrict, suspend and/or prohibit any Developer from using any Service of the TapTap Platform at its sole discretion. If your Games contain elements that may seriously damage Users’ devices or data, TapTap may disable the Games or remove the related Game package or disconnect the related link at its sole discretion.
-
-If your Game is removed from TapTap Platform due to above reasons and a User purchased such Game within a year (or a longer period as local consumer law mandates) before the date of removal, you agree to refund to the User all amounts paid by such User for such Game.
-
-9.3 **You hereby confirm and agree that Game reviews, ratings and posts are regarded as User Data, and the rights therein or thereto shall belong to Users or/and the TapTap Platform. If any third party uses User Data by reprinting or copying them without authorization, TapTap has the right to maintain the rights in its own name, including without limitation by sending a notice, filing a lawsuit and issuing an administrative complaint. In order to protect the rights and interests of related Users, TapTap has the right to retain the pages related to the removed Games, including all Product information, User reviews, Game ratings, news and forum content after removal, except that it will no longer provide service related to Game downloads.**
-
-## 10. Developer Account
-
-You agree to maintain your Developer Account registered on the TapTap Platform and assume all responsibilities for all Games uploaded or released using your account. If you choose to use the SDK or other service provided by TapTap, you shall comply with the local laws and regulations and the rules of the TapTap Platform.
-
-## 11. TapSDK (or API) Service
-
-11.1 If you choose to use the TapSDK (or API) service, you shall ensure (and urge your Users to ensure) that your (and your Users’) use of this service does not violate local laws and regulations, nor infringe upon any third-party rights and interests; any consequence or loss arising therefrom shall be borne by you (and your Users).
-
-11.2 You understand and agree (and shall urge your Users to understand and agree) that when you (and your Users) use the service, **unless permitted by local laws and regulations and with TapTap's prior written approval, you (and your Users) shall not, nor agree, authorize or instruct any third party to engage in activities including but not limited to the following:**
-
-- Delete any copyright notice, trademark notice or other ownership notice contained in the service, including without limitation any behavior that damages all related intellectual property rights of TapTap;
-- Submit to any third party any content that mis-states or mis-implies that the service provided to you is owned, sponsored or endorsed by TapTap;
-- Publicize or provide explanatory information about illegal behavior, promote personal injury to any group or individual, or spread any virus, worm, defect, Trojan horse or other destructive content, etc.;
-- Reverse-engineer, edit or attempt to extract source code from the service or any part of the content related to the service, or obtain original data and other data, etc.;
-- Copy, change and modify the service or any data or related interactive data released during the operation of the service, including without limitation by using plug-ins, add-ons, or unauthorized third-party tools or services to access the service or related systems;
-- Create any website or application to reproduce or copy this service or the TapTap Platform.
-
-11.3 TapTap imposes certain limits on the number of service requests and concurrent requests you initiate each day. After the upper limit is reached, we may temporarily suspend our services to you (or your Users). As agreed upon, you (and your Users) shall mark the words "TapTap" or "Powered by TapTap" in the application correctly, completely and conspicuously.
-
-11.4 You (and your Users) shall make your own judgment on the content of the service, decide whether or not to use it, and bear all risks arising from the use of this service and its related content, including those arising from reliance on the authenticity, completeness, accuracy, timeliness and practicality of the service and its content. TapTap does not provide any warranty for that, nor bear responsibility to you (or your Users) for any consequence or loss caused by such risks.
-
-11.5 You agree (and shall have your Users’ prior consent) to grant TapTap free, permanent, irrevocable, non-exclusive and non-transferable rights and license to use your (and your Users’) logos or actions to promote your (and your Users’) use of this service during the term of this Agreement.
-
-11.6 If your application or service needs to collect any data from your Users, you must have prior consent from your Users, and collect the data only necessary for the operation of the application and realization of its functions. At the same time, you shall inform your Users about the purpose and scope of your collection of data and how you use such data to keep your Users informed.
-
-## 12. Privacy Policy and Data Protection
-
-12.1 In order to continuously innovate and improve the TapTap Platform, TapTap may collect certain usage statistics data through the TapTap Platform, including without limitation the information on how to use the platform.
-
-12.2 TapTap will conduct a summary study on the collected data in order to improve the TapTap Platform based on the needs of Users and Developers, and will retain such data in accordance with the TapTap [Privacy Policy](https://www.taptap.io/privacy-policy). To ensure the improvement of Games, TapTap will provide part of the Games’ data in the TapTap Platform on the Developer's backend. If you need additional data, you may apply to the TapTap Platform in writing, and TapTap will decide whether or not to provide such data.
-
-12.3 Except as otherwise set forth in the TapTap [Privacy Policy](https://www.taptap.io/privacy-policy), all rights in or to Platform Operation Data and User Data shall belong to TapTap and are the trade secrets of the TapTap Platform. Without the prior written consent of TapTap, you shall not use the said data for any purpose other than stipulated in this Agreement, nor provide the said data to any third party in any form.
-
-## 13. Termination of this Agreement
-
-13.1 This Agreement will remain in effect unless you or TapTap terminates this Agreement according to the following provisions.
-
-13.2 If you wish to remove Games and terminate this Agreement pursuant to Article 9.1, you shall notify TapTap in advance in writing notice, and this Agreement can be terminated only with TapTap's consent. Before any termination of this Agreement, you shall not remove any Game from the platform; otherwise you will be liable for breach of contract.
-
-13.3 Under any of the following circumstances, TapTap has the right to terminate this Agreement with a written notice:
-
-- You have breached this Agreement or a separate agreement executed with the TapTap Platform;
-- The other conditions for Game removal, Service suspension or termination as set forth in this Agreement have occurred or been reached;
-- The Service under this Agreement is terminated according to laws, regulations, judgments, arbitrations or as required by the government;
-- Due to *force majeure*, you cannot continue to use the Service or the TapTap Platform cannot provide the Service;
-- You no longer have the right to use Service provided by the TapTap Platform;
-- TapTap decides to no longer provide Service for the platform.
-
-13.4 **If this Agreement or the Service is terminated for any reason, TapTap may choose at its own discretion to retain or delete all data in your account or saved in the server of the TapTap Platform when you use the Service, including any data that you have not completed before the termination of the Service.**
-
-13.5 **If this Agreement or the Service is suspended/terminated for any reason, you shall handle issues related to data backup and with your Users, and you shall be responsible for compensating for losses caused to the TapTap Platform as a result.**
-
-## 14. Disclaimer
-
-14.1 **You understand and agree that the TapTap Platform provides the Service on an "as-is" and "available" basis. TapTap does not provide any warranty to you, so you agree to solely bear the risks of using the TapTap Platform. TapTap will do its best to provide you with Services that are consistent and secure; however, TapTap cannot warrant that any Service it provides is free of defect to any degree or extent, nor foresee and prevent all legal, technical and other risks at any time, including without limitation \*force majeure\*, viruses, Trojan horses, hacking, system instability, third-party service defects, government actions, etc. and service interruption, data loss and other losses and risks that may be caused by such reasons. Therefore, you agree that even if the Services provided by the TapTap Platform are defective, such defects are unavoidable by the current technology in the industry, so they will not be regarded as a breach of contract on the part of TapTap. At the same time, if you lose data or information due to such defects, you agree not to hold TapTap responsible.**
-
-14.2 **You shall bear all responsibilities and risks arising from your use of the TapTap Platform, the Service and any content acquired by Users via downloading or other channels through your above-mentioned behavior; you shall bear all responsibility for damages or data losses to your or your Users’ computer system or other equipment caused by such use.**
-
-14.3 **TapTap further represents that the TapTap Platform does not provide any express or implied warranty or conditions, including without limitation implied warranties and conditions of merchantability, suitability for specific purposes, and non-infringement upon the rights of others.**
-
-14.4 **In view of the particularity of network service, TapTap has the right to, without notice, change, suspend or terminate part or all Service at any time based on the overall performance of the TapTap Platform or related specifications and rules. If any loss is caused to you for that reason, you agree not to hold TapTap responsible for such losses.**
-
-14.5 **To provide you with a more comprehensive Service, TapTap has the right to overhaul, maintain or upgrade the platform or related equipment that provides the Service on a regular or irregular basis, which may cause related Services to be interrupted or suspended within a reasonable period of time. If any loss is caused to you for that reason, you agree not to hold TapTap responsible for such losses.**
-
-14.6 **The Service may be interrupted by \*force majeure\* or other risks during use of such Service. For the purpose of this Agreement, “\*force majeure\*” refers to an event that cannot be foreseen, overcome or avoided and has significant impact on one party or both parties hereto, including without limitation natural disasters, e.g. floods, earthquakes, epidemics and storms, and social incidents, e.g. war, riot, government action, etc. When such an event happens, TapTap will try its best to cooperate with the relevant organizations promptly in order to make repairs immediately. If any loss is caused to you for that reason, you agree not to hold TapTap responsible.**
-
-## 15. Limitation of Liability
-
-**You understand and agree that TapTap, its affiliates and its licensor will not be liable for any direct, indirect, incidental, special, derivative or punitive excessive damages caused by your behavior as mentioned above (including any loss of data), whether or not based on any relevant liability theory, and whether or not TapTap or its representatives have been notified or shall be aware of the possibility of such damages.**
-
-## 16. Compensation
-
-16.1 To the maximum extent permitted by law, you agree to indemnify and hold harmless TapTap, its affiliates and their respective directors, officers, employees, agents and authorized operators against any third-party claim, legal action, legal proceeding and litigation process, as well as all losses, claims, damages, expenses and expenditures (including reasonable attorney fees) in the event that (a) your use of the Service is in violation of this Agreement, or (b) your Games infringe upon any third party's any copyright, trademark, trade secret, trade dress, patent or other intellectual property rights, or defame any third party, or infringe upon any third party's rights of publicity or privacy.
-
-16.2 To the maximum extent permitted by law, you agree to indemnify and hold harmless the payment processor, its affiliates and their respective directors, officers, employees and agents against any third-party claim, legal action, lawsuit/arbitration and lawsuit/arbitration process, as well as all losses, claims, damages, expenses and expenditures (including reasonable attorney fees) due to taxes arising from or in connection with the Service or Games released on the TapTap Platform.
-
-16.3 If your operation is banned by the local government due to violation of local laws and regulations, or the local regulatory authority investigates and prosecutes your illegal operations, you shall solely bear responsibility for all losses arising therefrom. If it has adverse effects on the TapTap Platform, you shall bear all losses caused to TapTap and restore its reputation. In such case, TapTap has the right to terminate this Agreement at its own discretion. If you have violated laws, or the Games you provide contain illegal information, or may infringe upon a third party's legal rights, regardless of whether or not adverse consequences have arisen, TapTap has the right to require you to replace or modify the content immediately, or terminate this Agreement, and request that you compensate for losses, if any.
-
-## 17. Changes to Agreement
-
-TapTap may amend or update this Agreement at any time. TapTap will post a notice on this page and/or the Dashboard explaining the change to this Agreement. Please check this Agreement regularly. Such change will not be retrospective. The change will come into effect and be deemed accepted by you: (a) for Developers who register as a Developer after the change, it will come into effect immediately; or (b) for existing Developers before the change, the change will come into effect as of the date set forth in the notice, or come into effect immediately if required by the local laws. **If you object to the amendment to this Agreement, you may notify TapTap in writing and both parties may negotiate how to settle the dispute. You shall stop using the Service provided by the TapTap Platform during the period from the day you send a notice of dispute to TapTap until both parties reach an agreement or you decide to accept this Agreement.** You agree that by continuing to use the TapTap Platform, including the related Service provided by the platform, you accept the amendment to this Agreement.
-
-## 18. General
-
-18.1 This Agreement shall constitute a full legal instrument between you and TapTap, and your use of the TapTap Platform will be governed by this Agreement. TapTap reserves the right to amend the terms and conditions of this Agreement and the platform policy at its sole discretion at any time. Matters not covered in this Agreement shall be subject to a separate agreement, if any, entered into by both parties.
-
-18.2 You agree that even if the TapTap Platform does not exercise or enforce any legal rights or remedies under this Agreement (or enjoyed by TapTap under any applicable law), it shall not be deemed that TapTap has waived such rights, and TapTap is still entitled to such rights or remedies.
-
-18.3 You agree that any software, service, hardware, material and file disclosed by TapTap to you as a Developer shall be TapTap’s confidential information. Unless with written consent from TapTap, you shall not disclose TapTap’s confidential information to any third party. The said confidentiality obligations shall survive any termination of this Agreement.
-
-18.4 If any court with jurisdiction over such matters holds that any provision of this Agreement is invalid, TapTap will delete the provision from this Agreement without affecting the remaining provisions of this Agreement. The remainder of this Agreement shall remain valid and enforceable as well.
-
-18.5 **Use of the Service or Games on the TapTap Platform may be restricted by the laws and regulations of the region where the Service is used. You shall comply with all relevant laws, regulations or policies applicable to the jurisdiction where your Games are released or used. These laws, regulations or policies include restrictions on the location of Service use, Users, and the end purpose.**
-
-18.6 You or TapTap shall not assign or transfer the rights granted in this Agreement without the prior written consent of the other party. You or TapTap shall not assign or transfer the obligations under this Agreement to any third party without the prior written consent of the other party. Any other attempt to assign or transfer such rights or obligations shall be invalid.
-
-18.7 **All complaints arising from or in connection with this Agreement or the relationship between you and TapTap under this Agreement shall be governed by Singapore law. Furthermore, any dispute related to this Agreement shall be settled by both parties through friendly negotiation. If such negotiation fails, both parties agree to refer to and finally resolve the dispute by arbitration administered by the Singapore International Arbitration Centre (“SIAC”) in accordance with the Arbitration Rules of the Singapore International Arbitration Centre (“SIAC Rules”) for the time being in force, which rules are deemed to be incorporated by reference in this clause. The seat of the arbitration shall be Singapore. The language of arbitration shall be English. Nevertheless, you agree that TapTap has the right to apply for injunctive relief from a court within any jurisdiction.**
-
-18.8 TapTap may send you agreements, rules, notices, and reminders with respect to the Service in the form of web announcement, web reminder, email, message or in-site letter, or by express or post, etc., which shall be deemed delivered once released or sent in any manner above and binding upon you.
-
-18.9 The terms and provisions with respect to confidentiality and dispute settlement shall survive any expiration or termination of this Agreement. [End]
-
diff --git a/.ci/hk/en/store/store-faq.mdx b/.ci/hk/en/store/store-faq.mdx
deleted file mode 100644
index ecbda7e7e..000000000
--- a/.ci/hk/en/store/store-faq.mdx
+++ /dev/null
@@ -1,65 +0,0 @@
----
-title: FAQ
----
-import {FaqLink} from '/src/docComponents/doc';
-
-## 1. Register as a Developer
-
-### [1. How to register?](/store/#how-to-register)
-### [2. How to enter the developer center?](/store/#get-started)
-### [3. What is the difference between the business developer and the individual developer? Can I change from one to the other?](/store/#what-is-the-difference-between-the-business-developer-and-the-individual-developer-can-i-change-from-one-to-the-other)
-### [4. I am an independent developer. What should I fill into the title?](/store/#i-am-an-independent-developer-what-should-i-fill-in-the-title)
-### [5. If the developer account already exists, what should I do? Can I claim it?](/store/#if-the-developer-account-already-exists-what-should-i-do-can-i-claim-it)
-### [6. How do I change the provider title?](/store/#how-do-i-change-the-provider-title)
-### [7. Do I have to start uploading games immediately after I have registered successfully?](/store/#do-i-have-to-start-uploading-games-immediately-after-i-have-registered-successfully)
-
-
-## 2. Authority Management
-
-### [1. How to add a member or an administrator?](/store/store-admin/#add-member-or-master-administrator)
-### [2. How to add roles?](/store/store-admin/#add-roles)
-### [3. How to delete a member?](/store/store-admin/#delete-member)
-### [4. Can the Super Administrator be changed? Is it possible to set up a new Super Administrator?](/store/store-admin/#1-can-the-super-administrator-be-changed--is-it-possible-to-set-up-a-new-super-administrator)
-
-## 3. Developer Verification
-
-### [1. What is the developer verification?](/store/store-auth/#what-is-the-developer-verification)
-### [2. How can I be verified as a developer?](/store/store-auth/#how-can-i-be-verified-as-a-developer)
-### [3. How do I remove the developer verification?](/store/store-auth/#how-do-i-remove-the-developer-verification)
-
-
-## 4. Assets Requirements
-
-### [1. Icon requirements](/store/store-material/#icons)
-### [2. About game requirements](/store/store-material/#about)
-### [3. Video and screenshots requirements](/store/store-material/#video-and-screenshots)
-### [4. Cover image requirements](/store/store-material/#cover-image-on-top-of-game-page)
-### [5. Promotion image requirements](/store/store-material/#promotion-image)
-### [6. License](/store/store-material/#license)
-### [7. What should I pay attention to in order to pass the assets review?](/store/store-material/#what-should-i-pay-attention-to-in-order-to-pass-the-assets-review)
-
-## 5. Create Game
-
-### [1. Game creation process](/store/store-creategame/)
-### [2. Do I have to start uploading games immediately after I have registered successfully?](/store/#do-i-have-to-start-uploading-games-immediately-after-i-have-registered-successfully)
-### [3. Do I need to release my game immediately after it passes the review?](/store/store-creategame/#do-i-need-to-release-my-game-immediately-after-it-passes-the-review)
-### [4. Will TapTap accept all types of games?](/store/store-creategame/#will-taptap-accept-all-types-of-games)
-
-## 6. Claim, Transfer, and Unpublish a Game
-
-### [1. My game is already on TapTap. Can I claim it?](/store/store-creategame/#my-game-is-already-on-taptap-can-i-claim-it)
-### [2. Can the owner/provider of the game be changed?](/store/store-creategame/#can-the-ownerprovider-of-the-game-be-changed)
-### [3. What should I do if I need to remove my game from TapTap?](/store/store-creategame/#what-should-i-do-if-i-need-to-remove-my-game-from-taptap)
-
-## 7. Update Game
-
-### [1. How to update the game?](/store/store-update/)
-### [2. Can I change the title of the game?](/store/store-update/#can-i-change-the-title-of-the-game)
-
-## 8. Game Testing
-
-### [1. Testing on Android](/store/store-test/#testing-on-android)
-### [2. Test types on Android](/store/store-test/#test-types-on-android)
-### [3. Testing on iOS](/store/store-test/#testing-on-ios)
-### [5. What is a test page? Why should I create a test page for my game? How to create a test page on TapTap?](/store/store-test/#what-is-a-test-page-why-should-i-create-a-test-page-for-my-game-how-to-create-a-test-page-on-taptap)
-### [6. Resources](/store/store-test/#resources)
diff --git a/.ci/hk/en/store/store-material.mdx b/.ci/hk/en/store/store-material.mdx
deleted file mode 100644
index 56778caf5..000000000
--- a/.ci/hk/en/store/store-material.mdx
+++ /dev/null
@@ -1,149 +0,0 @@
----
-title: Assets Requirements
-sidebar_position: 40
----
-
-import {Red, Blue} from '/src/docComponents/doc';
-
-Those marked with * are required materials.
-
-## Icons*
-
-The size should be 512x512px, and the format must be JPG or PNG. Please keep the key content within the Safe Area as shown in the image below.
-
-
-
-![ ](/img/Assets-Requirements-1.png)
-
-## Text Materials*
-
-### Description
-
-In the game page, description is displayed below game videos and screenshots, and above rating & reviews.
-
-You can describe the genre, gameplay, and features of your game here.
-
-For the About to display normally on the TapTap app, do not leave blank lines in the text.
-
-
-
-
-### What's new
-
-Update log is displayed below description, and you can tap on **More** to view the complete log.
-
-To avoid misunderstandings, do not fill in the Update if your game is released for the first time or in pre-registration.
-
-Please keep the Update concise and clear and relevant to the content of this update.
-
-For the Update to display normally on the TapTap app, do not leave blank lines in the text.
-
-
-
-
-
-## Video and Screenshots*
-
-In the game page, videos and screenshots are displayed above the game title.
-
-The video will be displayed first. When the video is not uploaded, the screenshots will be displayed in the order of upload.
-
-![](https://capacity-files.lcfile.com/1QVoTrTNvsCFh8Ubl1TAqOfQ9yXDDrkc/Assets-V2en-5.png)
-
-### Video
-
-Format: MP4
-
-Size: up to 1GB
-
-Length: We would recommend your video to be about 20 seconds for players to best understand the key gameplay. It must not be longer than 1 minute.
-
-Resolution: 1280 x 720px and above, 1920 x 1080px recommended.
-
-**A cover image is required for the video.**
-
-The size for cover image is 1256x706px and the format must be JPG or PNG.
-
-The video will also appear on the card when your game is recommended on Home of TapTap. **This video is optional.**
-
-When there is no video, the promotion image will be displayed on the card. When neither the video nor the promotion image is available, the cover image on the game page will be displayed.
-
-As for the content of the video, we recommend you to present new characters, new gameplay, events, etc. We also recommend you to update the video regularly.
-
-![](https://capacity-files.lcfile.com/UUmqcFAWGiPrXXP4JpNQzX5HhLW8ReDl/Assets-V2en-6.png)
-
-
-### Screenshots
-
-Please make sure all screenshots are of the same size, and that the format must be JPG or PNG. We recommend at least 3 screenshots for your game. Do not upload the same screenshot repeatedly.
-
-The file size of the screenshots should not be too large, for it may lower the speed of uploading and the loading on the game page.
-
-For horizontal screenshot, the recommended ratio is 16:9, and the size should be 1280x720px and above.
-
-For vertical screenshot, the recommended ratio is 9:16, and the size should be 1280x720px and above.
-
-![](https://capacity-files.lcfile.com/hpw19sE7Pux0G5y0hckmyrvRF331KRaG/Assets-V2en-3.png)
-
-
-
-
-
-## Cover image on top of game page*
-
-### Purpose
-
-This image will be displayed on the top of game's detail page in TapTap. **This must be provided.**
-
-![](https://capacity-files.lcfile.com/bIvVd5o3nQxFNHbwSN16BlXVc50inGnq/Assets-V2en-4.png)
-
-
-### Best Practices
-
-The cover image on the game page is usually the first thing that players notice about your game. Therefore, we recommend you to highlight the main characters and logo of the game in the image. Make sure everything in the image is clearly displayed.
-
-Do not include text other than the game title.
-
-Please keep the focus of the image inside the safe area as shown below.
-
-### Size and Format
-
-The size of the cover image should be 1920x1080px (16:9) and the format must be JPG or PNG.
-
-Please keep the focus of the image, such as the logo or main characters inside the safe area of 1760x920px.
-
-![ ](/img/Assets-Requirements-8.png)
-
-## Promotion Image*
-
-### Purpose
-
-This image will be used on the game's card in Collections, Home and other locations in TapTap. **You can choose to provide this image.**
-
-When there is no promotion image, cover image on the top of game page will be displayed instead. When there is a video available, the promotion image will be displayed before the video is automatically played.
-
-### Best Practices
-
-For the promotion image, we recommend you to use assets for marketing or related to the latest version. Please make sure that the materials and logo are clearly visible, and all assets are updated regularly.
-
-Do not include text other than the game title.
-
-### Size and Format
-
-The size of the cover image on the game page should be 1920x1080px (16:9) and the format must be JPG or PNG.
-
-## License
-
-### Authorization
-
-If your game involves any sort of authorization, please provide a scanned copy of the agency authorization letter or IP authorization letter. If there are multiple levels of authorization, please provide scanned copies of all agency authorizations.
-
-The authorization letter must be JPG or PNG. Each image should not be bigger than 2M, and up to 10 images can be uploaded.
-
-## Q&A
-
-### What should I pay attention to in order to pass the assets review?
-
-Please make sure your game does not infringe the copyright or any other parties. If your game's materials involve another IP, please provide proof of authorization.
-
-The assets you provide must not contain information that is related to politics, gambling, violence, sexual or other illegal content.
diff --git a/.ci/hk/en/store/store-notifications.mdx b/.ci/hk/en/store/store-notifications.mdx
deleted file mode 100644
index e14418475..000000000
--- a/.ci/hk/en/store/store-notifications.mdx
+++ /dev/null
@@ -1,32 +0,0 @@
----
-title: Description of the Platform's automated notifications
-sidebar_position: 31
----
-
-import { Blue } from "/src/docComponents/doc";
-
-TapTap will trigger automatic notifications based on the developer's operational behavior. Currently, the platform sends automatic notifications based on the following scenarios. Developers can independently configure whether to receive notifications according to their actual needs.
-
-- Regarding sending objects: Currently, the platform notifications are mainly directed to the roles of super administrators and game master administrators, etc. Developers can go to the backstage Manage Permissions module to configure the required roles independently.
-- About sending forms: Now the platform supports sending forms such as email and in-site messages. Developers can go to the background Message Center-Reception Settings module to manage the relevant contact information.
-
-## Description of Automatic Notification Types
-
-一、 Developer Onboarding and Profile Management
-
-| Name of Notifications | Scene | Object | Send Form |
-| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------ | ------------ |
-| Notification of Developer Onboarding Audit Passed | Become a Certified Developer Audit Passed | Users who Submit to Become a Certified Developer | Mailbox |
-| Notification of Developer Onboarding Audit Failure | Fail to Become a Certified Developer | Users who Submit to Become a Certified Developer | Email |
-| Notification of Approval of Developer Information Change Review | For developers who have settled in, submit developer information change review and review has been passed | Super Administrator | Email, In-site Messages |
-| Notification of Developer Information Change Review Failure | The settled developer submitted a review for the change of developer information, but the review failed. | Super Administrator | Email, In-site Messages |
-
-
-
-二、Games on the Shelf
-
-| Name of Notifications | Scene | Object | Send Form |
-| ---------------- | ------------------------------------------------ | ---------- | ------------ |
-| Notification of Game Approval | After the developer submits the game, the game is reviewed and approved | Game Administrator | E-mail、In-site Message |
-| Game Review Failure Notification | Game review is rejected after developer submits game | Game Administrator | E-mail、In-site Message |
-| Game Timing Notification | The game is set to go on shelves at a scheduled time. After passing the review, it reaches the time when it is timed to go live | Game Administrator | E-mail、In-site Message |
diff --git a/.ci/hk/en/store/store-register.mdx b/.ci/hk/en/store/store-register.mdx
deleted file mode 100644
index 0ddc554c4..000000000
--- a/.ci/hk/en/store/store-register.mdx
+++ /dev/null
@@ -1,101 +0,0 @@
----
-title: Register As A Developer
-slug: /store
-sidebar_position: 30
----
-
-import { Blue } from "/src/docComponents/doc";
-
-## How To Apply
-
-Go to **[Developer Center](https://developer.taptap.io/)** >> Find **[Become a developer](https://developer.taptap.io/developer-apply/)** >> Select the type of developer (Registered/Verified) >> Fill in the information >> Submit for review (results available in 2 business days) >> After passing the review, you can log in to your account in Developer Center
-
-| **Type** | **Prerequisites** | **Required Information** | **Review** | **Developer Page** |
-| ------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------------------------- |
-| **Registered Developer** | Owning a valid [Tap.io](https://accounts.taptap.io/signup)account | Provider title, Contact email (requires verification) | No review required | Not available on [Tap.io](https://www.taptap.io/) |
-| **Verified Developer(Recommended)** | Owning a valid [Tap.io](https://accounts.taptap.io/signup) account | **Individual Developer**
Provider title, Developer name, Contact address, Front and back of identity document
Contact email (requires verification)
**Business Developer**
Provider title, Company name, Contact address, Company's business license
Contact email (requires verification)
| Requires review by TapTap | Not available on [Tap.io](https://www.taptap.io/) |
-
-![](/img/DC-v2/register-V2en-1.png)
-
-## What are the differences between registered developer and verified developer?
-
-- **Registered Developer**
- - Your account will be marked as 'Registered Developer' in **Provider Settings** of your developer page in Developer Center. Registered information can be modified and it will be automatically approved after submitted for review. If you would like to become a verified developer, you can start the process by clicking on 'Become A Verified Developer'.
- - Registered developers only have the provider's permissions such as publisher settings, permission management, and tickets, as well as permissions related to game operation such as releasing games, accessing data overview, submitting qualification documents, etc.
-- **Verified Developer**
- - Your account will be marked as 'Verified Developer' in **Provider Settings** of your developer page in Developer Center. Registered information can be modified and submitted changes will need to be reviewed.
- - In addition to provider's permissions such as publisher settings, permission management, and tickets, as well as permissions related to game operation such as releasing games, accessing data overview, submitting qualification documents, etc; verified developers also have finance permissions such as setting up financial entity and game service billing, and access to game service such as TDS technical services and data analysis.
-
-When you are a registered developer and need to release paid games on TapTap, after choosing to add provider's financial entity information, you will be redirected to the page for 'becoming a verified developer'.
-
-When you need to enable some game services function on the developer page, after clicking on **configuration**, you will also be redirected to the page for becoming a verified developer.
-
-## Submit For Review
-
-After the application for verified developer is submitted, the TapTap operation team is expected to complete the review in 2 business days. The result will be available in **Notifications** of your account.
-
-You can also view the review status on the application page:
-
-- If the status is 'Under review', the page cannot be resubmitted. Please wait for the review result.
-- If the status shows 'Rejected', please refer to the reason for rejection to revise the application and resubmit for review.
-
-## Developer Page
-
-Once your developer account has been approved, you can go to Developer Center **Developer Center** to access your developer page.
-
-![](https://img.tapimg.com/market/images/2634c08f2c78303ed9daba736d9d308b.png)
-
-## Q&A
-
-### Can I switch between registered developer and verified developer?
-
-Currently TapTap does not support the switch between types of developer. Please choose the type of developer according to your situation. We recommend that you apply to become a verified developer.
-
-### I am an Individual developer. How do I fill in the provider title?
-
-We would recomend you to use a title of a team as the provider title instead of your own name.
-
-### If the developer account already exists. Can I claim it?
-
-You can contact us via **Developer Center** >> **Support**. You can also send your request to our team at [international_operation@taptap.com](mailto:international_operation@taptap.com) and provide a scanned copy of the business license or other materials that can prove that you are an employee if the provider. Please send the request under the same name as the existing developer.
-
-**Template**
-
-> **Email subject** Claim Provider Account On TapTap - Provider Title
->
-> Title of the vendor to be claimed:
->
-> Scanned copy of business license or other proof materials (you can also upload as attachments):
->
-> Contact of the applicant:
-
-When your application is approved, you will be able to claim the provider account and you will become the provider's Master Administrator by default.
-
-TapTap operation team will process your request in 2 business days.
-
-### How to change the provider title?
-
-You can contact us via **Developer Center** >> **Support**. You can also send your request to our team at [international_operation@taptap.com](mailto:international_operation@taptap.com) and provide a scanned copy of the business license or other materials that can prove that you are an employee of the provider.
-
-**Template**
-
-> **Subject**
-> Change provider title on TapTap - Provider's Current Title
->
-> Title of the provider title to be changed:
->
-> New provider title:
->
-> Scanned copy of business license or other proof materials (you can also upload as attachments):
->
-> Briefly explain the reason for change:
->
-> Contact of the applicant:
-
-TapTap operation team will process your request in 2 business days.
-
-### Do I have to start uploading games immediately after I have registered successfully?
-
-No.
-
-TapTap does not have requirements on the time to creat game, so feel free to set up the game according to your own schedules. We do recommend you to upload the games as early as possible to get better prepared for the release.
diff --git a/.ci/hk/en/store/store-test.mdx b/.ci/hk/en/store/store-test.mdx
deleted file mode 100644
index 75054a545..000000000
--- a/.ci/hk/en/store/store-test.mdx
+++ /dev/null
@@ -1,77 +0,0 @@
----
-title: Game Testing
-sidebar_position: 60
----
-
-import {Red, Blue, Black, Gray} from '/src/docComponents/doc';
-
-## Testing on Android
-
-### Contact Us
-
-Please give us details for the game test you are planning by sending an email to [international_operation@taptap.com](mailto:international_operation@taptap.com).
-
-**Template**
-
-> **Subject**
->
-> Test Information - XXX (Game Title)
-
----
-
->
-> Game Title:
->
-> Is it a closed test:
->
-> In-site page link:
->
-> Pre-download on (leave blank if not applicable): yyyy-mm-dd, XX:XX (UTC+08:00)
->
-> Server opens on (leave blank if not applicable): yyyy-mm-dd, XX:XX (UTC+08:00)
->
-> Will the data be deleted after the test is over:
->
-> Does this test include in-app purchases:
-
-When the test starts, TapTap will send notifications to Android users who have pre-registered for your game.
-
-### Change Status
-
-In addition to setting the release status in the update game,You can change the game status into "Testing" in the Store >> Change Release Status, then submit it for review. After 15 minutes, the game status will be changed automatically.
-
-Please note that you need to upload an APK and pass the review first. You will not be able to change the game status without an approved APK.
-
-![ ](/img/Game-Testing-1.png)
-
-## Testing on iOS
-
-For games to be released on iOS, you will need to test it with TestFlight.
-
-Please inform us about the testing time (UTC+8:00) and the public link of TestFlight via email at least two business days before the test starts. We will help you to configure the public link. You will need to change the status to Testing when the test starts.
-
-iOS users can join the test by clicking the Download button on the game page in the browser, which will guide them to install TestFlight. For users who have already installed TestFlight, the app will be launched and they will be eligible for joining the test.
-
-## Test Types on Android
-
-1. Unlimited Participants
-
- Refer to [Testing on Android]. Do not forget to inform us about your test via email.
-
-2. Limited Participants
-
- - Close Download
-
- You can choose to close the download when the number of participants reaches the limit. You can set the status to ‘Pre-registration’ or ‘Coming Soon’ instead.
-
- - Limited Codes
-
- You need to upload the activation codes in the backend in advance and notify us about the test via email. We will configure and distribute the codes according to your schedule.
-
-![ ](/img/Game-Testing-2.png)
-
-## Resources
-
-We will notify our editors about the test. During the test period, our editors will support your game by assigning resources such as visibility on TapTap.
-
-
diff --git a/.ci/hk/en/store/store-update.mdx b/.ci/hk/en/store/store-update.mdx
deleted file mode 100644
index 5a10be153..000000000
--- a/.ci/hk/en/store/store-update.mdx
+++ /dev/null
@@ -1,54 +0,0 @@
----
-title: Update Game
-sidebar_position: 55
----
-
-import { Blue } from "/src/docComponents/doc";
-
-### Location
-
-After the release, we recommend you to regularly update the materials on the game page to keep players interested and increase conversions. You can set up the update by clicking on the **>New** button in **Developer Center** >> **Store Info**.
-
-- When creating a new version, the page will automatically read the last filled content. You can only modify the information that needs to be updated.
-
-![](/img/DC-v2/update-V2en-1.png)
-
-You can schedule the release time for the update in the **Release Settings**.
-
-- If you select **Release Now** in Release Settings, the new version of the information/APK will be updated to the game page immediately after passing the review.
-- If you select **Scheduled** , the new version of the information/APK will be updated to the game page at the decided time after it passed the review.
-
-Please note that you will need to set the time to at least 24 hours after the current time.
-
-### Drafts
-
-When creating a new version, you can submit the filled information immediately for review, or you can save it as a draft.
-
-- After saving the draft, it will be loaded automatically when you open the page again.
-- You can only save one draft. Saving a new draft it will overwrite the existing one.
-
-![](/img/DC-v2/update-V2en-2.png)
-
-### Submit For Review
-
-After the update is submitted for review, the result should be available within 24 hours. Please remember to check your notifications.
-
-### Version History
-
-You can check the review status, APK version name, version code, and other information of all past versions in **Developer Center** >> **Version History**. Or you can click on **Version Details** to see all the information submitted for that version.
-
-## Q&A
-
-### Can the game title be changed?
-
-Yes.
-
-When changing game name, you will also need to change the relevant assets such as icon, posters, screenshots and other materials and submit them for review.
-
-After the game title has been changed, we recommend you to post an announcement on TapTap where you explain to the players the reason for the change to avoid confusion or the rating.
-
-### Can I change the name of the package files?
-
-Yes, but it is not recommended.
-
-Changes of the package file name will cause the players to lose the data and have to re-install the game. If you are sure that you need to change the package name, we recommend that you post an announcement on TapTap to explain the situation to the players. And you will need to sent your request via ticket at **Developer Center** >> **Support**. Or you can contact our team at [international_operation@taptap.com](mailto:international_operation@taptap.com).
diff --git a/.ci/hk/env.ts b/.ci/hk/env.ts
deleted file mode 100644
index 22085daa9..000000000
--- a/.ci/hk/env.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export const BRAND: string = "tds";
-export const REGION: string = "global";
-
-// Cloud Engine
-export const CLI_BINARY: string = BRAND === "tds" ? "tds" : "lean";
-export const HAS_SUB_DOMAIN: boolean = REGION === "global";
-export const HAS_ENGINE_CDN_DOMAIN: boolean = REGION === 'cn'
diff --git a/.ci/hk/sidebars.js b/.ci/hk/sidebars.js
deleted file mode 100644
index 51858820f..000000000
--- a/.ci/hk/sidebars.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// @ts-check
-
-/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
-const sidebars = {
- store: [
- {
- type: "link",
- label: "关于 TapTap",
- href: "https://www.taptap.io/about-us/",
- },
- {
- type: "autogenerated",
- dirName: "store",
- },
- ],
- sdk: [
- {
- type: "autogenerated",
- dirName: "sdk",
- },
- ],
- design: [
- {
- type: "autogenerated",
- dirName: "design",
- },
- {
- type: "link",
- label: "品牌素材",
- href: "https://www.taptap.io/about-us/brand-resources",
- },
- ],
-};
-
-module.exports = sidebars;
diff --git a/.ci/hk/zh-Hans/operations/manual.mdx b/.ci/hk/zh-Hans/operations/manual.mdx
deleted file mode 100644
index 3cb89b905..000000000
--- a/.ci/hk/zh-Hans/operations/manual.mdx
+++ /dev/null
@@ -1,552 +0,0 @@
----
-title: 内容运营
----
-
-## 用户能在哪里看到游戏及相关内容?
-
-### 【App 主要版块&资源位分布】- 以下所有资源均由运营配置
-
-
-
-
-
-### 内容撰写 & 发布指南
-
-- **建议发帖形式及字数**:建议以Article形式创建帖子,包含视频(预览显示为视频转化效率更高)+图片+文字内容,包含重要资讯(如上线、开测等)帖子的长度一般建议在 250 - 350 词左右
-
-- **帖子发出前必须检查**:
- - *帖子是否绑定游戏*:在帖子右侧边栏有 Mentioned games 版块,此处至少需要绑定一个游戏,否则帖子不会在首页进行分发,并且不会出现相关游戏的详情页中;帖子中插入的游戏卡片会自动同步到这里
- - *帖子状态是否是 Public *:右下角 Visibility 版块,帖子状态需为 Public ,若为 Unlisted,则无法正常分发
- - *帖子是否有封面*:可在个人主页检查草稿箱里的帖子是否有缩略图/封面图
-
-- **标题**:需包含游戏名、事件、事件发生时间这三个要素,方便玩家快速抓住有效信息,可选择性添加游戏描述(Zombie shooter xxx; indie puzzle game xxx; 抓典型特征)
-
- e.g.
-
- *Fortnite new season update coming on Apr xx!*
-
- *Madtale | Closed beta test available now!*
-
- *Text-based puzzle game CaseCracker coming to TapTap on xxx!*
-
-- **Call to action**:在帖子开头结尾强调希望玩家进行的动作,比如 Pre-register for xxx, Join our official Discord server, Participate in our closed beta test 等
-- **游戏简介**:一到两句即可,尽量放在开头,方便未玩过游戏的玩家了解游戏;可从游戏详情页的介绍中截取一些能概括游戏品类玩法特征的内容
-
- e.g.
-
- *Life Makeover is a limitless dress-up and social simulation game where you can create your very own avatar, customize dress-up and makeup, design one-and-only garment by yourself, build your dream house and chill with your besties!*
-
-- **图片&GIF 动图**:推荐在帖子中添加游戏 KV 图/ GIF 动图等,增加帖子可读性
-- **插入游戏卡片**:可在帖子开头插入此游戏的卡片,玩家点击游戏卡片可直接下载/预约,或跳转至游戏的详情页/直接点击下载/预约按钮,在开头插入可帮助加强转化效果
-
| 需要平台审核 | 在[Tap.io](https://www.taptap.io/)不展示 |
-
-![](/img/DC-v2/Register-as-Develper-V2-1.png)
-
-## 注册开发者/认证开发者区别
-
-- **注册开发者**
- - 开发者后台 **厂商设置** 处会标记「注册开发者」身份,厂商注册信息支持修改,提交后自动通过审核;页面上增加「成为认证开发者」入口,点击可跳转到「认证开发者填写页面」;
- - 注册开发者仅拥有厂商权限(厂商设置、权限管理、工单等)、游戏运营(上架游戏、查看概览、提交资质等)相关权限;
-- **认证开发者**
- - 开发者后台 **厂商设置** 处会标记「认证开发者」身份,厂商注册信息支持修改,修改后需提交审核;
- - 认证开发者拥有厂商权限(厂商设置、权限管理、工单等)和游戏运营(上架游戏、查看概览、提交资质等)相关权限,还拥有财务权限(设置财务主体、游戏服务账单)以及游戏服务(TDS 技术服务功能、数据分析)权限。
-
-如果您当前为「注册开发者」,需要在 TapTap 上架付费游戏,点击补充厂商财务主体时,系统就会自动触发跳转注册「认证开发者」界面。
-
-如果您当前需要使用「游戏服务」的功能,点击 **游戏配置** 页面,系统也会自动跳转至注册「认证开发者」的界面。
-
-## 提交审核
-
-认证开发者申请提交后,TapTap 运营团队预计会在 2 个工作日内完成审核,审核结果会在 个人中心-通知 中告知您。
-
-您也可以在申请开发者账号的页面中查看当前审核状态:
-
-- 若状态显示为「审核中」,此时无法重新提交申请,请耐心等待审核结果;
-- 若状态显示为「审核失败」,请您按照驳回原因修改后重新提交审核;
-
-## 后台入口
-
-当您的开发者账号审核通过后,点击进入 **开发者中心**,即可进入开发者后台。
-
-![](https://img.tapimg.com/market/images/9f2a0358f9f38cdb7bd1da7fe4699a27.png)
-
-## Q&A
-
-### 注册开发者与认证开发者身份可以互相转换吗?
-
-当前 TapTap 暂不支持开发者身份的相互转换,**请根据实际情况选择开发者类型进行申请,我们更建议您申请成为「认证开发者」。**
-
-### 个人开发者没有公司,如何填写厂商名称?
-
-我们建议您以团队或工作室名称而非个人名称作为厂商名称,例如:xxx 工作室。
-
-### 厂商名称已存在,如何进行厂商账号认领?
-
-您可以通过 **开发者后台**>>**工单** 联系我们,或将您的需求发送至运营部邮箱 [international_operation@taptap.com](mailto:international_operation@taptap.com),请提供企业运营执照或其他能证明您是该厂商官方人员的材料,并以相同厂商名称提交开发者申请。
-
-**邮件格式**
-
-> **邮件主题** TapTap 厂商账号认领申请 - XXX 厂商名称
->
-> 需认领的厂商名称或厂商页链接:
->
-> 营业执照扫描件或相关证明材料(可以附件形式上传) :
->
-> 申请人联系方式:
-
-您的开发者申请审核通过后,即完成开发者账号认领,您将自动成为该厂商主管理员。
-
-TapTap 运营人员预计将在 2 个工作日内处理完毕。
-
-### 如何修改厂商名称?
-
-您可以通过 **开发者后台** >> **工单** 联系我们,或将您的需求发送至运营部邮箱 [international_operation@taptap.com](mailto:international_operation@taptap.com),并提供企业营业执照扫描件或其他能证明您是该厂商官方人员的材料。
-
-**邮件格式**
-
-> **邮件主题**
-> TapTap 厂商名称变更申请 - XXX 厂商原名称
->
-> 需修改的厂商名称或厂商页链接:
->
-> 新厂商名称:
->
-> 营业执照扫描件或相关证明材料(可以附件形式上传) :
->
-> 申请修改厂商名称原因(简述):
->
-> 申请人联系方式:
-
-TapTap 运营人员预计将在 2 个工作日内处理完毕。
-
-### 开发者账号审核通过后需要立即进行游戏入库吗?
-
-不需要。
-
-TapTap 对创建游戏时间无要求,请开发者根据自己的时间安排进行游戏创建;TapTap 建议开发者尽早进行游戏入库,积累预约用户。
diff --git a/.ci/hk/zh-Hans/store/store-test.mdx b/.ci/hk/zh-Hans/store/store-test.mdx
deleted file mode 100644
index 973ab17d5..000000000
--- a/.ci/hk/zh-Hans/store/store-test.mdx
+++ /dev/null
@@ -1,72 +0,0 @@
----
-title: 游戏测试
-sidebar_position: 60
----
-
-import {Red, Blue, Black, Gray} from '/src/docComponents/doc';
-
-## 安卓端测试流程
-
-### 测试信息同步
-
-确认测试时间后,请将游戏测试信息发送至运营部邮箱[international_operation@taptap.com](mailto:international_operation@taptap.com)
-
-**邮件格式**
-
-> **邮件标题]**
-> TapTap 游戏开测信息同步 - XXX 游戏名称
->
-> 游戏名称:
->
-> 是否为限量测试:
->
-> 站内页面链接:
->
-> 预下载时间(无预下载需求可不填):X月X日 XX:XX
->
-> 开服日期、时间(无服务器写开下载时间):X月X日 XX:XX
->
-> 删档/不删档
->
-> 计费/不计费
-
-游戏开测后,TapTap 运营人员将主动向游戏安卓预约用户进行通知推送。
-
-### 测试状态变更**
-
-你需要在开测前一天将游戏状态修改为测试,并上传 apk 进行审核,apk 置为当前的时间需要与开放测试的时间保持一致,通过审核后,游戏详情页的按钮会在测试开始的时候变更为「开放下载」。
-
-![ ](/img/Game-Testing-1.png)
-
-### iOS 端测试流程
-
-TapTap 仅支持以 TestFlight 形式进行 iOS 端游戏测试。
-
-请在邮件中同步本次测试时间和 TestFlight 公开链接,运营同学会帮你将公开链接配置到后台中,随后将 iOS 游戏状态改为「测试 」即可。
-
-用户可以通过点击网页版 TapTap 游戏详情页的「下载」按钮申请测试,将会引导他安装 TestFlight。
-若已经安装,会唤起 TestFlight 软件,并且直接获取下载权限。
-
-### 安卓端测试形式
-
-1. 不限量测试
-
- 安卓不限量测试请参考[安卓测试流程],及时上传 apk 并与 TapTap 运营人员同步测试信息。
-
-2. 限量测试
-
- - 限定人数关闭下载
-
- 到达期望下载人数后,可将安卓商店状态修改为「预约」或「敬请期待」,关闭下载。
-
- - 激活码限量测试
-
- 你需要提前在后台上传激活码,并邮件通知 TapTap 运营同步限量测试具体信息,TapTap 运营将为你进行操作。
-
-![ ](/img/Game-Testing-2.png)
-
-## 测试资源
-
-TapTap 运营将会与编辑同步游戏测试信息,TapTap 不会对游戏进行评级,编辑将根据游戏测试时间以及游戏品质安排游戏测试资源位。
-
-
diff --git a/.ci/hk/zh-Hans/store/store-update.mdx b/.ci/hk/zh-Hans/store/store-update.mdx
deleted file mode 100644
index 593a88f18..000000000
--- a/.ci/hk/zh-Hans/store/store-update.mdx
+++ /dev/null
@@ -1,54 +0,0 @@
----
-title: 游戏更新
-sidebar_position: 55
----
-
-import { Blue } from "/src/docComponents/doc";
-
-### 更新位置及入口
-
-游戏上架后,建议您时常更新在架游戏详情页素材,这样可以提升游戏的下载转化率和站内的曝光度,您可以在 **开发者后台** >> **商店资料** 位置点击 **构建新版本** 完成更新操作。
-
-- 构建的新版本会自动读取已上线版本填写的详情页内容,您可以在此基础上对需要更新的内容进行修改
-
-![](/img/DC-v2/update-V2en-1.png)
-
-更新游戏同样支持定时上线功能,您可以在 **发布设置** 中选择当前更新版本的发布时间
-
-- 若发布设置中选择 **「立即发布」** ,新版本素材/APK 通过审核后即会更新到游戏详情页中;
-- 若发布设置中选择 **「定时上线」** ,通过审核后,新版本素材/APK 会在设置的时间发布到游戏详情页中。
-
-注意:定时上线时间目前仅支持选择当前时间 24h 之后的时间
-
-### 草稿功能
-
-在新创建的未发布版本中,填写完更新内容后,您可选择保存为草稿或者直接提交审核。
-
-- 保存草稿 >> 再次打开游戏页面可以直接读取当前存储的草稿内容。
-- 目前仅可储存一份草稿,再次保存草稿时将会自动覆盖前一版本。
-
-![](/img/DC-v2/update-V2en-2.png)
-
-### 提交审核
-
-游戏更新审核提交后,预计将会在 24 小时内得到审核结果,审核结果将会在个人中心-通知中心告诉您。
-
-### 查询版本记录
-
-您可以在 **开发者后台** >> **版本记录** 中查询所有历史版本的审核状态、操作日志、APK 版本名称、版本号等信息,也可以点击 **版本详情** 查看该版本提交的所有内容。
-
-## Q&A
-
-### 游戏名称可以修改吗?
-
-可以。
-
-修改游戏名称时,请同步修改游戏图标、推广图、截图等资料中的涉及到游戏名称的信息并提交审核。
-
-我们建议游戏修改名称后,在站内发布内容帖向玩家说明修改游戏名称的原因,以免给玩家带来不良游戏体验,影响游戏评分。
-
-### 游戏包名可以修改吗?
-
-可以,但是不建议您修改。
-
-包名变更会导致之前安装过的玩家出现存档丢失,需要重新安装等情况,如确定需要修改,我们建议您在站内发布内容贴向玩家说明修改游戏包名的原因,并将您的需求提交到 **开发者后台** >> 工单 系统中,或通过运营部邮箱 [international_operation@taptap.com](mailto:international_operation@taptap.com) 联系我们。
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 6c973f2c7..6c23c3165 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -13,7 +13,7 @@ jobs:
- name: Checkout Repository
uses: actions/checkout@v2
with:
- token: ${{ secrets.VERSION_TOKEN }}
+ token: ${{ secrets.CHECKOUT_TOKEN }}
- name: Deploy to LeanEngine
uses: enflo/curl-action@v1.2
diff --git a/docs/aws-partner.mdx b/docs/aws-partner.mdx
deleted file mode 100644
index e8e4d7fd2..000000000
--- a/docs/aws-partner.mdx
+++ /dev/null
@@ -1,76 +0,0 @@
----
-title: TapTap is an AWS software partner for global game developers
-slug: /aws-partner/
----
-
-import useBaseUrl from "@docusaurus/useBaseUrl";
-
-TapTap—China's active mobile game community—has officially become Amazon's software partner. The two parties will aggregate each other's technology and publishing capabilities, starting from 0, to help developers going overseas to solve the problem of early going overseas, reduce developers costs, and provide overseas games more comprehensive support.
-
-TapTap is a zero-commission app store that lets gamers download gaming applications onto Android mobile phone while content providers are able to take 100% revenue. Meanwhile, TapTap has a robust community of developers and gamers that safely use the application. Since TapTap includes a community of developers that can add their apps to the platform, multiple versions of the same game can be found like TapTap PUBG.
-
-
-
-
-
-TapTap is trying to remodel the 30% revenue cut taken by Apple’s App Store and Google Play — announcing a 0% revenue share with game developers. After two years of exploration and development, TapTap International has users in more than 170 countries (regions). In April this year, the new version of TapTap International was launched, serving overseas players with a new look and exciting functions. TapTap Developer Service (TDS) is also gradually opening more functions in the international version to provide support for overseas developers and domestic overseas manufacturers.
-
-
-
-
-
-AWS helps TapTap International Edition to provide Amazon Web Services Cloudfront, Amazon EC2, Amazon RDS, and other services, and the global cloud infrastructure provides support for TapTap International Edition. As the pioneer and leader of cloud computing, AWS provides more than 200 full-featured cloud computing services around the world. AWS provide almost unlimited scalability, so we can scale our application automatically as we continue to grow and add new customers.
-
-Through cooperation with AWS, Taptap has upgraded the underlying architecture, improved the security of the architecture, expanded international delivery capabilities, and improved the experience of gamers and developers.
-
-
-
-
-
-Up to now, a number of games such as "Sausage Man" and "Flash Party" have completed the whole process of publishing, testing and launching through the TapTap international version. In addition, TapTap's unique community system also brings novel and effective operation methods for the game's overseas platform distribution. TapTap International's "Sausage Man" launched in July 2021. Its game forum has gathered more than 1.4 million players and published nearly 3,000 discussion posts. Developers hold various activities in the community to enhance user stickiness and retention.
-
-Benefiting from TapTap's zero-commission business model, well-known game developers such as Epic Games have also partnered with TapTap. The worldwide popular Fortnite is now officially exclusive to TapTap, offering download, installation and forum services. Currently, Fortnite has recorded over 10 million downloads on TapTap.
-
-
-
-
-
-According to XD’s financial report in 2021, the MAU of the TapTap international version has grown to 12.24 million, and it still maintains a high growth rate. Based on tens of millions of MAUs, in addition to the basic distribution capabilities, TapTap has the ability to provide more platform-based one-stop services for domestic developers going overseas. For example, "bonfire testing" or "beta testing" can help developers acquire seed users and provide a sufficient and diverse population for each level of testing. Users help developers tune the game through the mechanism of community feedback and grow in testing. For global testing, developers generally cannot easily open Google and iOS stores in key countries such as the United States, Germany and France during the testing period. Only some small-volume countries such as the Philippines, Canada, etc. can be opened for testing. With the help of TapTap, we can introduce more users from core countries, especially users in Tier-1 countries. The number of users in TapTap international is quite stable, which help developer solve the problem of user volume during the test period. In global testing process, advertising purchases are often limited by the insufficient cold start speed and the small purchase area. It is difficult to import sufficient number of users on the test day or the next day.
-
-
-
-TapTap Developer Service (TDS) is releasing its service capabilities for TapTap international developers after repeated verifications in China. Taking the first batch of “Flash Party” that received a version number this year as an example, the game launched on “TapTap International” in February 2022, and access the TapTap Developer Service (TDS) Tap Login, friend system, embedded community and other functions open up the social system for "Flash Party", which is convenient for players to exchange games and view strategies. At the same time, various operational activities can directly reach players in the game, improving player retention and duration.
-
-
diff --git a/leancloud/docs/classroom.mdx b/docs/classroom.mdx
similarity index 100%
rename from leancloud/docs/classroom.mdx
rename to docs/classroom.mdx
diff --git a/docs/community/advanced.mdx b/docs/community/advanced.mdx
deleted file mode 100644
index b5d43cfd3..000000000
--- a/docs/community/advanced.mdx
+++ /dev/null
@@ -1,329 +0,0 @@
----
-title: 挑战进阶操作,玩转社区运营
-sidebar_label: 挑战进阶操作
-sidebar_position: 30
----
-
-## 4.1 如何做好一次活动
-
-![做好活动](/img/events-keypoints.jpg)
-
-社区活动是促进论坛活跃、与玩家高效交流的有力途径。TapTap 强活跃高反馈的社区属性,决定了此处的玩家相较于一般游戏发行平台、信息发布渠道、玩家自发聚集地等,拥有更频繁的社交潜力与创作潜力,这就要求游戏开发者要更注重社区活动带来的种种益处。
-
-
-
-TapTap is trying to remodel the 30% revenue cut taken by Apple’s App Store and Google Play — announcing a 0% revenue share with game developers. After two years of exploration and development, TapTap International has users in more than 170 countries (regions). In April this year, the new version of TapTap International was launched, serving overseas players with a new look and exciting functions. TapTap Developer Service (TDS) is also gradually opening more functions in the international version to provide support for overseas developers and domestic overseas manufacturers.
-
-
-
-
-
-AWS helps TapTap International Edition to provide Amazon Web Services Cloudfront, Amazon EC2, Amazon RDS, and other services, and the global cloud infrastructure provides support for TapTap International Edition. As the pioneer and leader of cloud computing, AWS provides more than 200 full-featured cloud computing services around the world. AWS provide almost unlimited scalability, so we can scale our application automatically as we continue to grow and add new customers.
-
-Through cooperation with AWS, Taptap has upgraded the underlying architecture, improved the security of the architecture, expanded international delivery capabilities, and improved the experience of gamers and developers.
-
-
-
-
-
-Up to now, a number of games such as "Sausage Man" and "Flash Party" have completed the whole process of publishing, testing and launching through the TapTap international version. In addition, TapTap's unique community system also brings novel and effective operation methods for the game's overseas platform distribution. TapTap International's "Sausage Man" launched in July 2021. Its game forum has gathered more than 1.4 million players and published nearly 3,000 discussion posts. Developers hold various activities in the community to enhance user stickiness and retention.
-
-Benefiting from TapTap's zero-commission business model, well-known game developers such as Epic Games have also partnered with TapTap. The worldwide popular Fortnite is now officially exclusive to TapTap, offering download, installation and forum services. Currently, Fortnite has recorded over 10 million downloads on TapTap.
-
-
-
-
-
-According to XD’s financial report in 2021, the MAU of the TapTap international version has grown to 12.24 million, and it still maintains a high growth rate. Based on tens of millions of MAUs, in addition to the basic distribution capabilities, TapTap has the ability to provide more platform-based one-stop services for domestic developers going overseas. For example, "bonfire testing" or "beta testing" can help developers acquire seed users and provide a sufficient and diverse population for each level of testing. Users help developers tune the game through the mechanism of community feedback and grow in testing. For global testing, developers generally cannot easily open Google and iOS stores in key countries such as the United States, Germany and France during the testing period. Only some small-volume countries such as the Philippines, Canada, etc. can be opened for testing. With the help of TapTap, we can introduce more users from core countries, especially users in Tier-1 countries. The number of users in TapTap international is quite stable, which help developer solve the problem of user volume during the test period. In global testing process, advertising purchases are often limited by the insufficient cold start speed and the small purchase area. It is difficult to import sufficient number of users on the test day or the next day.
-
-
-
-
- PUBG: NEW STATE open second alpha test TapTap International
-
-
-
-TapTap Developer Service (TDS) is releasing its service capabilities for TapTap international developers after repeated verifications in China. Taking the first batch of “Flash Party” that received a version number this year as an example, the game launched on “TapTap International” in February 2022, and access the TapTap Developer Service (TDS) Tap Login, friend system, embedded community and other functions open up the social system for "Flash Party", which is convenient for players to exchange games and view strategies. At the same time, various operational activities can directly reach players in the game, improving player retention and duration.
-
-
-
- Flash Party Moments
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/design/design-login.mdx b/i18n/en/docusaurus-plugin-content-docs/current/design/design-login.mdx
deleted file mode 100644
index a5631065f..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/design/design-login.mdx
+++ /dev/null
@@ -1,217 +0,0 @@
----
-title: TapTap Login Button Design Guideline
-sidebar_label: TapTap Login Button Design Guideline
-sidebar_position: 1
-slug: /design
----
-
-import { Background, Figure } from "/src/docComponents/doc";
-import useBaseUrl from "@docusaurus/useBaseUrl";
-
-## Types of Login Buttons
-
-
-
-
-
-
-## Icon
-
-### Composition of information
-
-The icon is consisted of the TapTap logo and a background.
-
-### Button Shape
-
-The default shape of the button is circle. It can be changed to a square with rounded corners to match the appearance of other in-game buttons.
-
-
-
-
-
-
-### Button size
-
-The default size is 40*40 pt. Please make sure that the aspect ratio of the icon is always 1:1.
-
-### Button margin
-
-The materials in the downloadable resource packs already contain the correct amounts of padding. Please do not cut them out or add any extra padding.
-
-## Pill
-
-### Composition of information
-
-The icon is consisted of the TapTap logo, text, and a background.
-
-
-
-
-
-
-### Rounded corners
-
-Defaults to the maximum radius (pill). The radius can be adjusted to match the appearance of other buttons in the game interface.
-
-
-
-
-
-
-
-### Button size
-
-The default height is 40pt and the button width varies depending on the length of text in different languages.
-
-### Button text
-
-The recommended text is "Log in with TapTap". There should be no line breaks or spaces within "TapTap".
-
-
-
- The TapTap logo and text
- >
- }
- imgSrc={useBaseUrl("https://capacity-files.lcfile.com/gA4wwKXJqAqkpAusEh9zU6BaxpXjBXM5/io-design-2.2.1.png")}
- imgAlt=""
- />
-
-
-
-
-## Colour rules for login portal
-
-Depending on the background color of the game interface, you can choose from one of the two default colors: black and the brand color (green).
-
-### Black
-
-Use this style on a light background that has enough contrast to allow the text to appear in white if you are using a black background for the buttons.
-
-
-
-
-
-
-### Brand color (green)
-
-Use this style on a dark background with sufficient contrast, with black text if using the brand color as the button's background. It is recommended to add a black circular background image under the TapTap logo to enhance the overall look.
-
-
-
-
-
-
-### Rules for customization
-
-As the TapTap Login SDK can be used in a variety of mobile games, game developers may make minor adjustments to the TapTap logo, but must maintain the brand identity of the TapTap logo. There are no restrictions on borders, backgrounds, or button text, but changes to the style and color of the TapTap logo are not allowed.
-
-
-
-
-
-
-
-## Rules for the arrangement of multiple login options
-
-The size and style of the login button's background can be adjusted to match the overall style of the buttons in the game by changing the background of the buttons to any shape, but it must be consistent with the style of the other login buttons.
-
-
-
-
-
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/design/design-moment.mdx b/i18n/en/docusaurus-plugin-content-docs/current/design/design-moment.mdx
deleted file mode 100644
index 044f7c71c..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/design/design-moment.mdx
+++ /dev/null
@@ -1,270 +0,0 @@
----
-title: Embedded Moments Design Guideline
-sidebar_position: 2
----
-
-import { Background, Figure } from "/src/docComponents/doc";
-import useBaseUrl from "@docusaurus/useBaseUrl";
-
-## Navbar Labels
-
-### Color Schemes and Applications
-
-Navbar labels are the textual elements used for menu items. They can be in both light and dark colors.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-### Contrast Is Important
-
-The more contrast you set between the text and the background, the more legible the text will be. If you pick a light background for your page, you should set your text in a dark color, and vice versa.
-
-
-
-
-
-
-
-
-
-
-
-## Backgrounds
-
-### Background Sizes
-
-Embedded Moments can be displayed in both landscape and portrait modes. This means that you need to provide two background images for your game, one for each orientation.
-
-
-
-
-
-### Cropping Backgrounds
-
-When Embedded Moments is opened on a device with a short screen, the background image will be cropped to fit the screen.
-
-
-
-
-
-### Designing Background Images
-
-#### Style
-
-The background image shall not be too prominent that it takes the user’s attention away from the main content. Therefore, we suggest that you add simple patterns on the background and keep the contrast within the background to a minimum.
-
-
-
- A background that doesn’t catch the user’s attention
- - Fewer colors
- - Low contrast between the foreground and the background
- >
- }
- imgSrc={useBaseUrl("/img/io/design-moment/2.3.1.1.png")}
- imgAlt=""
- />
-
- A background that catches the user’s attention
- - Too many colors
- - Too much contrast between the foreground and the background
- >
- }
- imgSrc={useBaseUrl("/img/io/design-moment/2.3.1.2.png")}
- imgAlt=""
- />
-
-
-#### What to Place in the Background
-
-You may add patterns or illustrations to the background as long as they don’t get too much attention from the user.
-
-
-
-
-
-
-
-
-
- - The contrast is too strong
- - The illustration is too complex
- >
- }
- imgSrc={useBaseUrl("/img/io/design-moment/2.3.2.2.2.png")}
- imgAlt=""
- />
-
-
-
-
-
- - Cluttered decorations
- - Decorations occupy too much space
- >
- }
- imgSrc={useBaseUrl("/img/io/design-moment/2.3.2.3.2.png")}
- imgAlt=""
- />
-
-
-#### Safe Zones
-
-To ensure that the entirety of the illustrations in the background can be seen by the user, please keep the illustrations within the safe zones defined below.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-#### Background Color of the Sticky Tab List
-
-You can set a background color for the sticky tab list. The tab list will fit well with the rest of the UI if you pick the color from the top area of the background image.
-
-
-
-
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/achievement/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/achievement/_category_.json
deleted file mode 100644
index 236d14f6a..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/achievement/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "成就系统",
- "collapsed": true,
- "position": 7
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/achievement/bestpractice.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/achievement/bestpractice.mdx
deleted file mode 100644
index 92bc6d7e5..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/achievement/bestpractice.mdx
+++ /dev/null
@@ -1,61 +0,0 @@
----
-title: 《泰拉瑞亚》 使用 Tap 成就来吸引更多玩家
-sidebar_label: 最佳实践
-sidebar_position: 3
----
-
-《泰拉瑞亚》是一个跨越手机、PC、主机平台的常青树游戏,在全球都有海量的忠实玩家。在中国大陆,中国港澳台发行的手机版《泰拉瑞亚》,目前在 TapTap 上销量也超过了 200 万份,收获了 9.3 的 Tap 评分。
-
-
-## 支持跨平台的 TDS 成就
-由于泰拉瑞亚可以在多个平台和渠道发行,他们使用了不受平台和引擎的限制的 TDS 的成就系统,不论游戏发布在 iOS AppStore、Android 各大渠道、PC、甚至主机平台,都能帮助游戏实现跨平台的成就系统。
-截止到 2022 年 5 月底,游戏总计触发成就人数超过 200 万人,解锁白金成就(即获得了全成就)的玩家超过 3500 人,游戏社区中也有不少玩家晒自己的成就进度、讨论成就的具体达成方法。
-
-![img](https://capacity-files.lcfile.com/zqc1dU4x4cV06hcCok4CwCxFWoowsf4v/achievement_show_on_taptap.png)
-
-
-我们也即将在未来几个月在 TapTap 客户端内增加更多成就系统相关的功能和露出,包括在动态显示好友获得的成就、对比成就、成就专题页等,为喜欢成就的玩家提供更多的实用和分享功能,也能帮助接入了成就的游戏进行更好的宣发和曝光。
-
-
-## 白金成就
-
-我们会为那些获得 TapTap 玩家和 TapTap 编辑认可的游戏,提供白金奖杯,以此来激励玩家冲击游戏全成就,并且白金成就也可作为一种社交资产被玩家所拥有。
-
-如果您的游戏符合至少以下条件,就可以在开发者中心后台申请开通白金成就,我们的编辑会来评估您的游戏是否可以开通白金:
-- 游戏时长在同类游戏中处于正常水平
-- 免费游戏中,用户不需要付费,仍然可以获得全成就
-- 付费游戏中,用户不需要强制购买主线流程外的额外 DLC 或内购,仍然可以获得白金成就
-
-![img](https://capacity-files.lcfile.com/3kBnhO30aI8ukszjt61L4527zHbyAaW5/baijin_achievement.png)
-
-
-## 成就稀有度
-成就的稀有度在一定的冷启动数据后,会开始自动计算。稀有度可以反映这个成就的获得难度,对于稀有度极高的成就,玩家挑战完成会有强烈的成就感。
-![img](https://capacity-files.lcfile.com/C15DpYOKoz9DBFRcUmhy85GDX9Mv8PRT/achievement_rare.png)
-
-
-## 开发者中心的成就配置
-成就后台的配置也较为简单,并且可以看到每一个成就的解锁人数、完成率等数据。
-![img](https://capacity-files.lcfile.com/dTh8PAoMPzbySH1Vw1D6XvF9gswNY6KK/achievement_list.png)
-
-
-## 接入成就的方法
-
-目前成就系统提供 SDK 上报成就与服务端上报成就 2 种方式。
-
-《泰拉瑞亚》采用了服务端的方式接入了 TDS 成就系统,这样能够比较好的做好防作弊的处理,也能比较及时和稳定的做到成就的同步。
-
-如果您的成就已经存储在服务端,那么我们优先建议您使用服务端上报的方式。
-
-如果您的游戏是个单机游戏,没有服务端,或者没有存储过用户成就,那么也可以选择接入 TapSDK 来上报玩家的成就。当您设置到成就的触发点后,玩家联网时 SDK 会上报成就数据;若玩家在断网状态,SDK 也会在本地存储成就数据,待网络恢复后上报。
-在后台配置完成就,并确保您的游戏已经接入完成并且测试无误后,您就可以点击发布,将成就发布到 TapTap 上了。
-
-
-## 将玩家的成就展示在 TapTap
-
-如果希望在 TapTap 客户端内的成就列表中看到您的游戏,那就需要在游戏中接入 TapTap 登录。您可以把 TapTap 登录作为游戏的一种登录方式,可以 将 TapTap 登录作为游戏账号可以绑定的一个第三方账号,专门用于做成就的同步。
-
-
-## 立即开始使用 TDS 成就服务
-
-如果希望了解如何接入TDS 成就系统,可以访问我们的 [成就产品指南](/sdk/achievement/features/) 和 [成就开发指南](/sdk/achievement/guide/)。整个系统的接入非常简单,有任何问题也欢迎通过 TapTap 开发者中心的工单系统来与我们取得联系。
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/achievement/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/achievement/features.mdx
deleted file mode 100644
index eb2357482..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/achievement/features.mdx
+++ /dev/null
@@ -1,241 +0,0 @@
----
-title: Achievements Introduction
-sidebar_label: Introduction
-sidebar_position: 1
----
-
-import useBaseUrl from '@docusaurus/useBaseUrl';
-
-The Achievements service allows you to configure and release game achievements on the Developer Center. Players can unlock the achievements you configure as they play your game. Those who unlock all of your game’s achievements can also earn a Platinum Achievement.
-
-
-## Our Benefits
-
-**For game developers**:
-- Reduce development costs: Developers don’t have to build the low-level logic of an achievement system themselves. They can release an achievement system in their games by following a simple setup process.
-- Increase customer lifecycle value: Improve player engagement by creating a user growth system and incentivizing players as they progress through your game.
-- Enable data analysis: Developers can evaluate the difficulty of the games they create by looking at the data collected by the achievement system.
-
-**For players**:
-- Enhance the game experience: Players will enjoy your game more when you add fun and challenging achievements to your game.
-- Evoke emotions: By instilling a sense of honor and a desire to collect, players will be more likely to stick with your game.
-
-
-## Glossary
-
-### Overview
-
-| Term | Definition | Rules |
-| --- | --- | --- |
-| Achievement ID | A unique identifier used to report the achievement progress to the SDK. You can customize the achievement ID of an achievement. | Can only contain letters and numbers and cannot be longer than 100 characters. |
-| Name | A short name for the achievement, such as “Experienced Driver”. | Can be up to 80 characters long. |
-| Description | A short description of the achievement used to tell players how to unlock it, such as “Avoid 10 obstacles in a row”. | Can be up to 400 characters long. |
-| Icon | A square icon to illustrate the achievement. | Upload a 512×512 color image in PNG or JPG format. TDS will automatically generate a grayscale variant that will be displayed if the achievement hasn’t been unlocked yet. |
-| Initial Status | The initial status of the achievement. Can be either hidden or displayed. | Cannot be changed once the achievement is released. |
-| Hidden Achievement | Players cannot see the achievement details. | If selected, TDS will provide a special description and icon for the achievement if it hasn’t been unlocked yet. |
-| Displayed Achievement | Players can see the achievement details. | This is the default option. If selected, the achievement description will be visible to everyone. |
-| Category | The category of a normal achievement. | A normal achievement can only have one category. You cannot change the category of an achievement unless it is unreleased. |
-| Main Achievements | One of the two categories of achievements, containing all the achievements for the initial release of the game. | In most cases, there should be at least 10 achievements for a game. The number of main achievements is fixed for the lifetime of a game. |
-| DLC Achievements | One of the two categories of achievements that includes the achievements that come with the game’s DLCs. | There is no limit to the number of DLC achievements that can be created for a game. |
-
-### Accumulation Achievement
-
-Accumulation achievements allow the player to incrementally approach the goals for unlocking achievements over time. As the player works toward the goals, your game can continuously report the progress to TDS so that TDS can keep track of the progress and send notifications to your game when certain achievements are unlocked. Your game can then notify the player that they have unlocked the achievements.
-
-**Configuring steps**: When you create an accumulation achievement, you must specify the number of steps required to unlock the achievement (between 2 and 100,000,000). The achievement is unlocked for a player when they complete the steps required for the achievement, even if the achievement is hidden. TDS keeps track of the steps a player completes for each achievement, so you don’t have to.
-
-**Steps cannot be reset**: Steps for an accumulation achievement accumulate over time and cannot be deleted or reset in-game. An example of a proper accumulation achievement is “Win 20 games”, while “Win 5 games in a row” is not an accumulation achievement because the progress is reset when the player loses a game. Similarly, “Own 2,000 coins” cannot be an accumulation achievement because players can spend coins as well as earn them. You could, however, set up the latter two achievements as basic achievements and have them unlocked for the player as they complete them, but you’d have to keep track of the progress yourself.
-
-### Rarity
-
-This is the percentage of players who have unlocked the achievement. The lower the number, the rarer the achievement.
-
-Rarity = Number of players who have unlocked the achievement / Number of players who have initialized the achievement
-- 50% ≤ Common ≤ 100%
-- 10% ≤ Uncommon < 50%
-- 1% ≤ Rare < 10%
-- Ultra Rare < 1%
-
-The image below shows the different rarity levels:
-
-![img](https://capacity-files.lcfile.com/ekmGiWHqhUBcbWxlslbqmptgvUM0YUCh/achievement02.png)
-
-### Unlock Status
-
-The unlock status of an achievement is updated when the player reaches the goal you configured for the achievement.
-- Locked: The default status of an achievement, meaning that the player hasn’t unlocked it yet.
-- Unlocked: The player has already unlocked the achievement. There are two cases:
- - Basic achievement: The status of the achievement becomes *Unlocked* when the player reaches the goal.
- - Accumulation achievement: A percentage is displayed to indicate how many steps the player has completed. Once all the steps have been completed, the status of the achievement becomes *Unlocked*.
-
-The image below shows the different unlock statuses an achievement can have:
-
-![img](https://capacity-files.lcfile.com/Ln6jBCdAHYNMQUYuKgX3tM2HutRwshLk/achievement01.png)
-
-### Platinum Achievement
-
-For quality games, if a player unlocks all the **Main Achievements**, they will also unlock the **Platinum Achievement**.
-
-**Main Achievements** only include those that have been released.
-
-
-
-## Features
-
-### Create Basic Achievements
-
-To create an achievement for your game, go to **Developer Center > Game Services > Cloud Services > Achievements** and click on **Create**.
-
-After filling in all the information, click on **Save**. The status of the achievement will change to **Ready**.
-
-![img](https://capacity-files.lcfile.com/lGQMKMLDecXlBHk81mx1gLEnlpaW4Wcn/io-achievement04.png)
-
-### Edit Basic Achievements
-
-To edit an existing achievement before releasing all of your achievements, click on **Edit**. You will see an interface similar to the one used to create an achievement. Here you can edit the details of the achievement.
-
-Once achievements are released, each achievement’s **Achievement ID**, **Accumulation Achievement**, **Category**, and **Initial Status** settings cannot be changed anymore.
-![img](https://capacity-files.lcfile.com/OnBx19wreaA077dklPHSToigELWqo8p7/io-achievement05.png)
-
-### Release Basic Achievements
-
-Your achievements will remain in **Ready** status while you are still editing them. Once you have at least **5** normal achievements and have tested them all, you can release them by clicking on **Release Normal Achievement**. Please be careful as this will release all achievements to the production environment.
-
-![img](https://capacity-files.lcfile.com/MbgyWimo7v1WShObhuSSMLKpgTffBVvE/io-achievement06.png)
-
-### Delete/Reset Achievements
-
-Before releasing achievements, you can **delete** or **reset the progress** of an achievement using the buttons at the end of the achievement entry.
-
-Once achievements are released, **they cannot be deleted or reset**.
-
-![img](https://capacity-files.lcfile.com/O1Uo3NM5sVsVbwJ6yN1lc7zoDkDQUE7q/io-achievement07.png)
-
-### Apply for a Platinum Achievement
-
-To ensure the quality of Platinum Achievements, our reviewers will evaluate the quality of your game when you apply for a Platinum Achievement for it. Your game will only qualify for a Platinum Achievement if our reviewers determine that your game is of above-average quality.
-
-![img](https://capacity-files.lcfile.com/28Lyi8vJSvNo9qYmuE4dlopxMV3wA0zF/io-achievement08.png)
-
-### Create a Platinum Achievement
-
-Once your game qualifies for a Platinum Achievement, you can click on **Create** to create a Platinum Achievement. A Platinum Achievement requires an **icon**, **name**, and **description**.
-
-![img](https://capacity-files.lcfile.com/IPjkqIE5KwHAMzL8i3aof2zG4oN39VKX/io-achievement09.png)
-
-### Release a Platinum Achievement
-To ensure that your game’s Platinum Achievement is challenging enough, you will not be able to release the Platinum Achievement until you have at least **10 Main Achievements**.
-Keep in mind that once the Platinum Achievement is released, **you will no longer be able to create Main Achievements for your game**. You will only be able to create DLC Achievements.
-
-
-
-### Languages
-
-To set up multiple languages, go to **Developer Center > Game Services > Configuration** and click on **Languages**.
-
-![](https://capacity-files.lcfile.com/hNyOSJxHfooDgI2iUQwWSFnJx5pNyFpJ/io-achievement10.png)
-
-In the pop-up window, select the languages you want to use for the achievements in your game, and then click on **Confirm**. You can remove any added languages from the list on the right.
-
-![](https://capacity-files.lcfile.com/ChpxOosYo5aNbT5Kg7EDectggvNOcdqb/io-achievement11.png)
-
-Once you have completed the above steps, you can click on the **+** icon in the achievement editing window and select the languages you have added. You will then be able to set up different languages for the **Name** and **Description**. The icon and the other configurations will be the same for all the languages and you won’t need to fill them in again.
-
-![](https://capacity-files.lcfile.com/Q1g0pXpR8OTGMlexzizV5IT8WEyOSVF8/io-achievement12.png)
-
-
-
-### Displaying Achievements in Game
-
-- TapSDK: Using the user interface provided by the TapSDK, players can view their unlocked and locked achievements by tapping a button provided by your game.
-- API: You can also choose to implement your own user interface and access the achievement data through our API.
-
-![img](https://capacity-files.lcfile.com/P7pD6xiO2KlkAxXUFzvwEA5zry2tVa23/achievement13.png)
-
-### In-Game Toasts
-
-- When the player performs an action that unlocks one or more achievements, a toast will appear at the top area of the screen. Only one achievement can be displayed at a time. Additional achievements will be queued and displayed one at a time.
-- You can choose whether to show or hide the achievements page and the toasts.
-
-![img](https://capacity-files.lcfile.com/DdEv3pd3kitPheTXD47vMhTjF1LpHv5r/achievement14.png)
-
-![img](https://capacity-files.lcfile.com/DSXcQaiQirx31spBfuQvsjvzQxE2Nyok/achievement15.png)
-
-### Displaying Achievements on TapTap
-- If you use TapTap Login (with game released on TapTap): Players logged in with TapTap Login can view their unlocked and locked achievements under “TapTap app > Profile > About”.
-- If you don’t use TapTap Login: If your game has its own account system, you’ll need to create a feature that links a player’s account to their TapTap account and syncs their achievements to their TapTap account.
-- At this time, only games released in the Mainland China region can have achievements displayed on TapTap.
-
-:::tip
-**Two scenarios for linking TapTap accounts**
-- Scenario 1: The user has an account created without TapTap Login (Account A) and a TapTap account that hasn’t been used to log in to the game. Now the game would be able to link the two accounts. Both accounts have the same TDS user ID and the TapTap account would display Account A’s achievements.
-- Scenario 2: The user has an account created without TapTap Login (Account A) and a TapTap account that has already been used to log in to the game (Account B). Currently, the TapTap account shows the achievements of Account B. In order for the TapTap account to show the achievements of Account A, your game must provide a feature that allows the user to unlink Account B from the TapTap account and then link Account A to the TapTap account.
-:::
-
-![img](https://capacity-files.lcfile.com/RxtLqJKJPAbHRz5qNJ3bD0wUx0UF4MY3/achievement_show_on_taptap1.png)
-
-
-## Integrating the Service
-
-### Getting Started
-
-1. Become a TapTap developer;
-2. Create your game in the TapTap Developer Center and enable “TDS Authentication” for the game;
-3. Request to enable the Achievements service by submitting a ticket. To allow sub-accounts to have access to the service, go to “Manage Permissions” and give the “Game Administrator” permission to the accounts;
-4. Download the TapSDK (v3.2.0 or higher) and integrate it into the game.
-
-### Workflow
-
-![img](https://capacity-files.lcfile.com/exAftqivPqahCSJlTjBYNR3vnkjXmYWc/achievement16.png)
-
-### Developer Guide
-
-See **[Achievements > Guide](/sdk/achievement/guide/)**.
-
-### Testing
-
-1. After you have added all the achievements for your game, you can set some users as **testers** for the **production environment**;
-2. The testers will be able to test the achievements with the **Ready** status;
-3. You can [reset the achievement data](#deletereset-achievements) at any time during testing;
-4. If the achievement data is not reset before you release your game, the data will be transferred to the production environment and mixed with the data generated by other users. It’s up to you if you want to keep the data after testing.
-
-:::note
-To add testers and have them test the achievements with the **Ready** status, please contact the TDS technical support team by opening a ticket on the Developer Center. In the ticket, please provide the `Client ID` of the application and the `Object ID` returned after the test account implements TapTap Login.
-:::
-
-
-## FAQ
-
-#### Can I still apply for a Platinum Achievement after releasing my game?
-
-Yes. You can apply for a Platinum Achievement either before or after you release your game.
-
-#### Can I release new achievements while my Platinum Achievement application is being reviewed?
-
-Yes. Applying for and creating a Platinum Achievement doesn’t prevent you from releasing other achievements.
-
-#### If a player has already earned all the achievements before my Platinum Achievement application is approved, will they still receive the Platinum Achievement?
-
-Yes. The player will automatically receive the Platinum Achievement.
-
-#### If my game already uses its own account system or third-party login, how can I integrate TDS Authentication into my game?
-
-For existing users, you can let them log in with their existing accounts and then invoke the TapSDK’s account linking interface to link those accounts to TapTap. The players will then have their IDs created with TDS Authentication. The TapSDK will then be able to determine the identity of each player using the TDS User IDs. For new users, you can just let them log in with TapTap without going through these steps.
-
-#### If I integrate the Achievements service into my existing game, will players still get the achievements they’re supposed to get?
-
-There are two ways to award achievements to users of your existing game:
-- Sync the expected achievement data with the Achievements service using our API.
-- If your game already tracks the achievements your players have earned, you can convert those achievements to the appropriate Achievement IDs and submit them all at once with the SDK when it’s convenient (you may want to temporarily turn off toasts so the players don’t see them).
-
-#### If a player already has the Platinum Achievement and I add a new achievement, will the player lose the Platinum Achievement?
-
-No. The Platinum Achievement is only dependent on main achievements. The achievements you create after the Platinum Achievement will be DLC achievements, which won’t affect the Platinum Achievement.
-
-#### If a player can create multiple profiles in my game and earn achievements multiple times, what would happen when I submit the same achievements to the SDK?
-
-Each TapTap account can only receive each achievement once, so it won’t be possible for a player to receive the same achievement multiple times.
-
-#### I’m getting `Empty sign or session` when invoking the interface for initializing the achievement data. What could be causing this?
-
-As the Achievements service is based on TDS Authentication (`TDSUser`), please make sure that you have integrated TDS Authentication into your game and instantiated the `TDSUser` object before initializing the achievements data (`[TapAchievement initData];`). If `TDSUser` is empty, you will see `Empty sign or session`.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/achievement/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/achievement/guide.mdx
deleted file mode 100644
index da6cf7760..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/achievement/guide.mdx
+++ /dev/null
@@ -1,713 +0,0 @@
----
-title: Achievements Guide
-sidebar_label: Guide
-sidebar_position: 2
----
-
-import MultiLang from '/src/docComponents/MultiLang';
-import CodeBlock from '@theme/CodeBlock';
-import sdkVersions from '/src/docComponents/sdkVersions';
-import Languages from '../_partials/languages.mdx';
-
-This page shows you how to integrate the Achievements service into your game. The Achievements service is based on TDS Authentication (TDSUser). You can read more about this in **[TDS Authentication > Guide](/sdk/authentication/guide/)**.
-
-## Install the SDK
-
-Please make sure that you have already [created the game](/sdk/start/quickstart/#creating-a-game), [configured the project](/sdk/start/quickstart/#project-configuration), and [initialized the project](/sdk/start/quickstart/#initialization) according to the [TapSDK Quickstart](/sdk/start/quickstart/). Once you have completed these steps, you can proceed to add the `TapAchievement` module from the TapSDK downloaded from the [Downloads](/tap-download) page:
-
-
-
-
-{`"dependencies":{
- ...
- // Achievements service
- "com.taptap.tds.achievement": "https://github.com/TapTap/TapAchievement-Unity.git#${sdkVersions.taptap.unity}",
-}`}
-
-
-
-{`repositories{
- flatDir {
- dirs 'libs'
- }
-}
-dependencies {
- ...
- implementation (name:'TapAchievement_${sdkVersions.taptap.android}', ext:'aar') // TapTap Achievements service
-}`}
-
-
-
-{`// Achievements service
-TapAchievementResource.bundle
-TapAchievementSDK.framework`}
-
-
-
-
-## Register Callbacks
-The Achievements SDK has several callbacks that are triggered when the data is initialized, when the data cannot be initialized, and when there is a progress update. The data initialization callback is particularly important as it is required for the SDK to work properly. If the data cannot be initialized, please either prompt the user or try to initialize the data again at another time.
-
-
-
-<>
-
-**Prerequisite**
-
-TapTap.Achievement depends on the `TapTap.Bootstrap` library.
-
-**Namespace**
-
-```cs
-using TapTap.Achievement;
-```
-
-To register callbacks:
-
-```cs
-TapAchievement.RegisterCallback(IAchievementCallback callback);
-
-private class AchievementCallback:IAchievementCallback
-{
- public void OnAchievementSDKInitSuccess()
- {
- // The Achievement SDK is initialized
- }
-
- public void OnAchievementSDKInitFail(TapError errorCode)
- {
- if (errorCode != null)
- {
- // Failed to initialize the SDK
- }
- }
-
- public void OnAchievementStatusUpdate(TapAchievementBean bean, TapError errorCode)
- {
- if (errorCode != null)
- {
- // Failed to update achievements
- return;
- }
-
- if (bean != null)
- {
- // Achievements updated
- }
- }
-}
-```
-
->
-<>
-
-```java
-TapAchievement.registerCallback(new AchievementCallback() {
- @Override
- public void onAchievementSDKInitSuccess() {
- // Data loaded
- }
-
- @Override
- public void onAchievementSDKInitFail(AchievementException exception) {
- // Failed to load data; please try again
- }
-
- @Override
- public void onAchievementStatusUpdate(TapAchievementBean item, AchievementException exception) {
- if (exception != null) {
- // Failed to update achievements
- return;
- }
- if (item != null) {
- // item updated
- }
- }
-});
-```
-
->
-<>
-
-
-```objectivec
-[TapAchievement registerCallBack:self];
-
-- (void)onAchievementSDKInitSuccess {
- // Data loaded
-}
-
-// Failed to initialize the SDK
-- (void)onAchievementSDKInitFail:(nullable NSError *)error {
- // Failed to load data; please try again
-}
-
-// Achievements updated
-- (void)onAchievementStatusUpdate:(nullable TapAchievementModel *)achievement failure:(nullable NSError *)error {
- if (error) {
- // Failed to update achievements
- } else {
- // achievement updated
- }
-}
-```
-
->
-
-
-
-## Initialize the Data
-Since the Achievements service keeps track of the user’s achievement data locally, please make sure to **initialize the data when the user is logged in**. If the user switches to a different account, be sure to call the interface for initializing the data again. Failure to do so may result in the achievement data for different accounts being mixed up.
-
-This step is asynchronous, which means that you should not proceed until you have received a successful callback.
-
-
-
-```cs
-TapAchievement.InitData();
-```
-
-```java
-TapAchievement.initData();
-```
-
-```objectivec
-[TapAchievement initData];
-```
-
-
-
-## Get All Achievements
-You can get all achievements from either the local cache or the server. The local cache is refreshed when you successfully invoke the data initialization interface. It is also refreshed when you proactively invoke the data fetching interface.
-
-If you need to fetch the latest data from the server while the player is playing, you can use the data fetching interface. Most of the time you just need to load the data from the local cache.
-
-
-
-```cs
-// Get local data
-TapAchievement.GetLocalAllAchievementList((list, code) =>
-{
- if (code != null)
- {
- // Failed to get all achievements
- }
- else
- {
- // Got all achievements successfully
- });
-}
-// Fetch data from the server
-TapAchievement.FetchAllAchievementList((list, code) =>
-{
- if (code != null)
- {
- // Failed to fetch all achievements
- }
- else
- {
- // Fetched all achievements successfully
- });
-}
-```
-
-```java
-// Local data
-List allList = TapAchievement.getLocalAllAchievementList();
-
-// Server data
-TapAchievement.fetchAllAchievementList(new GetAchievementListCallBack() {
- @Override
- public void onGetAchievementList(List achievementList, AchievementException exception) {
- if (exception != null) {
- switch (exception.errorCode) {
- case AchievementException.SDK_NOT_INIT:
- // The SDK has not initialized the data yet
- break;
- default:
- // Failed to fetch data
- }
- } else {
- // Fetched data successfully
- }
- }
-});
-```
-
-```objectivec
-// Local data
- NSArray *allList = [TapAchievement getLocalAllAchievementList];
-
- // Server data
- [TapAchievement fetchAllAchievementList:^(NSArray *_Nullable result, NSError *_Nullable error) {
- if (error) {
- switch (error.code) {
- case 9001:
- // The SDK has not initialized the data yet
- break;
- default:
- // Failed to fetch data
- break;
- }
- } else {
- // Fetched data successfully
- }
- }];
-```
-
-
-
-## Get Current User’s Achievements
-You can get the current user’s achievements from either the local cache or the server. The local cache is merged with the server data when you successfully invoke the data initialization interface (if different steps are tracked for the same achievement in the server and the local cache, the larger one is kept). They are also merged when you proactively invoke the data fetching interface.
-
-The local data is often more accurate than the server data because the server data may be out of date due to previous synchronization failures.
-
-
-```cs
-// Get local data
-TapAchievement.GetLocalUserAchievementList((list, code) =>
-{
- if (code != null)
- {
- // Failed to get user’s achievements
- }
- else
- {
- // Got user’s achievements successfully
- });
-}
-// Fetch data from the server
-TapAchievement.FetchUserAchievementList((list, code) =>
-{
- if (code != null)
- {
- // Failed to fetch user’s achievements
- }
- else
- {
- // Fetched user’s achievements successfully
- });
-}
-```
-
-```java
-// Local data
-List userList = TapAchievement.getLocalUserAchievementList();
-
-// Server data
-TapAchievement.fetchUserAchievementList(new GetAchievementListCallBack() {
- @Override
- public void onGetAchievementList(List achievementList, AchievementException exception) {
- if (exception != null) {
- switch (exception.errorCode) {
- case AchievementException.SDK_NOT_INIT:
- // The SDK has not initialized the data yet
- break;
- default:
- // Failed to fetch data
- }
- } else {
- // Fetched data successfully
- }
- }
-});
-```
-
-```objectivec
-// Local data
- NSArray *userList = [TapAchievement getLocalUserAchievementList];
-
-// Server data
-[TapAchievement fetchUserAchievementList:^(NSArray *_Nullable result, NSError *_Nullable error) {
- if (error) {
- switch (error.code) {
- case 9001:
- // The SDK has not initialized the data yet
- break;
- default:
- // Failed to fetch data
- break;
- }
- } else {
- // Fetched data successfully
- }
- }];
-```
-
-
-
-## Get an Achievement (Directly)
-
-
-
-```cs
-// displayID is the Achievement ID you configured on the Developer Center
-TapAchievement.Reach("displayID");
-```
-
-```java
-// displayID is the Achievement ID you configured on the Developer Center
-TapAchievement.reach("displayID");
-```
-
-```objectivec
-// displayID is the Achievement ID you configured on the Developer Center
-[TapAchievement reach:@"displayId"];
-```
-
-
-
-## Add Steps to an Accumulation Achievement
-There are two ways to add steps to an accumulation achievement. The first is to call `growSteps` with the number of steps to add (for example, enter `5` to add `5` steps). The second is to use `makeSteps` with the total number of steps (for example, enter `100` to set the total number of steps to `100`). Internally, the SDK would calculate the total number of steps when you call `growSteps`.
-
-
-
-```cs
-// displayID is the Achievement ID you configured on the Developer Center
-TapAchievement.GrowSteps("displayID", step);
-TapAchievement.MakeSteps("displayID", step);
-```
-
-```java
-// displayID is the Achievement ID you configured on the Developer Center
-TapAchievement.growSteps("displayID", 5);
-TapAchievement.makeSteps("displayID", 100);
-```
-
-```objectivec
-// displayID is the Achievement ID you configured on the Developer Center
-[TapAchievement growSteps:@"displayID" numSteps:5];
-[TapAchievement makeSteps:@"displayID" numSteps:100];
-```
-
-
-
-## Set Toasts
-By default, the SDK automatically displays a toast when the player gets an achievement. You can turn off toasts using the following interface:
-
-
-
-```cs
-TapAchievement.SetShowToast(bool isShow);
-```
-
-```java
-TapAchievement.setShowToast(false);
-```
-
-```objectivec
-[TapAchievement setShowToast:NO];
-```
-
-
-
-## Show Achievements
-The SDK comes with a page that shows all the achievements the player has gotten:
-
-
-
-```cs
-TapAchievement.ShowAchievementPage();
-```
-
-```java
-TapAchievement.showAchievementPage();
-```
-
-```objectivec
-[TapAchievement showAchievementPage];
-```
-
-
-
-## Interpreting Achievement Data
-
-
-
-```cs
-public string displayId; // Achievement ID
-public int visible = VisibleFalse; // Whether the achievement is hidden
-public string title; // Name
-public string subTitle; // Description
-public string achieveIcon; // Icon
-public int step; // Total steps
-public bool fullReached; // Whether the player has gotten the achievement
-public int reachedStep; // Current steps
-public long reachedTime; // When the player has gotten the achievement
-public AchievementStats stats; // Rarity
-```
-
-```java
- /*base*/
- private String displayId; // Achievement ID
- private int visible = VISIBLE_TRUE; // Whether the achievement is hidden
- private String title; // Name
- private String subTitle; // Description
- private String achieveIcon; // Icon
- private int step; // Total steps
- private AchievementStats stats; // Rarity
- private int type; // Type; 1 means basic achievement; 99 means Platinum Achievement
- /*user*/
- private boolean fullReached; // Whether the player has gotten the achievement
- private int reachedStep; // Current steps
- private long reachedTime; // When the player has gotten the achievement
-```
-
-```objectivec
-@property (nonatomic, copy, readonly) NSString *displayId; // Achievement ID
-@property (nonatomic, copy, readonly) NSString *achieveIcon; // Icon
-@property (nonatomic, copy) NSString *title; // Name
-@property (nonatomic, copy, readonly) NSString *subTitle; // Description
-@property (nonatomic, assign, readonly) NSNumber *step; // Total steps
-@property (nonatomic, strong) TDSAchievementStatus *stats; // Rarity
-@property (nonatomic, assign) NSInteger type; // Type; 1 means basic achievement; 99 means Platinum Achievement
-
-// User data
-@property (nonatomic, assign) BOOL fullReached; // Whether the player has gotten the achievement
-@property (nonatomic, assign) long reachedTime; // When the player has gotten the achievement
-@property (nonatomic, assign) NSInteger reachedStep; // Current steps
-```
-
-
-
-## Internationalization
-
-The Achievements service supports multiple languages:
-
-:::tip
-When the data is initialized, only the achievement data for the current language is synchronized from the server. To switch to another language after initialization, call `fetchAllAchievementList` and `fetchUserAchievementList`.
-:::
-
-
-
-## REST API
-
-Now let’s look at the REST API provided by the Achievements service.
-You can write your own programs or scripts to invoke these interfaces to perform server-side administrative operations.
-
-### Request Format
-
-The body of any request sent to the REST API must be a JSON object. The `Content-Type` in the HTTP header should be `application/json`.
-
-Requests sent to the server are authenticated using the key-value pairs in the HTTP Header:
-
-Key|Value|Description
----|----|---
-`X-TDS-Id`|`{{clientId}}`|The game’s `Client Id`, which can be found on the Developer Center.
-`X-TDS-Server-Secret`|`{{serverSecret}}`|The game’s `Server Secret`, which can be found on the Developer Center.
-
-Find out more about application credentials [here](/sdk/storage/guide/setup-dotnet#credentials).
-
-In addition to providing the `Client Id` through the `X-TDS-Id` HTTP Header, you must also provide the same `Client Id` in the URL.
-
-When obtaining achievements, you must specify the language in the URL. See [Language Codes](#language-codes) for more information.
-
-
-If there is an error, the HTTP status code 500 will be returned, like:
-
-```json
-{
- "code": "500",
- "msg": "成就服务忙,稍后请求",
-}
-```
-
-### Get All Achievements
-
-Below is the interface for retrieving all the game’s achievements. Make sure you include the language in the URL.
-
-```sh
-curl -X GET \
- -H "X-TDS-Id: {{clientId}}" \
- -H "X-TDS-Server-Secret: {{serverSecret}}" \
- https://tds-tapsdk.cn.tapapis.com/achievement/open/v1/clients/{{clientId}}/achievements/languages/
-```
-
-The response body looks like this:
-
-```json
-{
- "success": true,
- "data": {
- "list": [
- {
- "achievement_id": "Achievement ID",
- "client_id": "Client ID",
- "achievement_open_id": "Achievement open ID (a custom achievement ID specified by the developer when creating the achievement; this is the unique identifier used when submitting achievement data with the SDK)",
- "achievement_type": "Achievement type; 1 means basic achievement; 99 means Platinum Achievement",
- "is_hide": "Whether the achievement is hidden; 0 means not hidden; 1 means hidden",
- "count_step": "Total steps; if the achievement is not an accumulation achievement, this will be 1",
- "show_order": "The order of the achievement; if the achievement is a Platinum Achievement, this will be 0",
- "achievement_config_out_dto": {
- "achievement_config_id": "The achievement’s configuration ID",
- "achievement_id": "Achievement ID",
- "language_id": "Language ID",
- "achievement_icon": "Link to the achievement’s icon",
- "achievement_title": "Achievement name",
- "achievement_sub_title": "Achievement description"
- },
- "achievement_rarity": {
- "rarity": "Rarity rate",
- "level": "Rarity; 1 means Common; 2 means Uncommon; 3 means Rare; 4 means Ultra Rare"
- }
- }
- ]
- }
-}
-```
-
-### Get a User’s Achievements
-
-Below is the interface for retrieving a user’s achievements. Make sure you include the player’s account objectId and language code in the URL.
-
-```sh
-curl -X GET \
- -H "X-TDS-Id: {{clientId}}" \
- -H "X-TDS-Server-Secret: {{serverSecret}}" \
- https://tds-tapsdk.cn.tapapis.com/achievement/open/v1/clients/{{clientId}}/users//achievements/languages/
-```
-
-The response body looks like this:
-
-```json
-{
- "success": true,
- "data": {
- "list": [
- {
- "achievement_id": "Achievement ID",
- "client_id": "Client ID",
- "achievement_open_id": "Achievement open ID (an ID assigned to the achievement when it is added on the Developer Center)",
- "achievement_type": "Achievement type; 1 means basic achievement; 99 means Platinum Achievement",
- "is_hide": "Whether the achievement is hidden; 0 means not hidden; 1 means hidden",
- "count_step": "Total steps; if the achievement is not an accumulation achievement, this will be 1",
- "show_order": "The order of the achievement; if the achievement is a Platinum Achievement, this will be 0",
- "achievement_config_out_dto": {
- "achievement_config_id": "The achievement’s configuration ID",
- "achievement_id": "Achievement ID",
- "language_id": "Language ID",
- "achievement_icon": "Link to the achievement’s icon",
- "achievement_title": "Achievement name",
- "achievement_sub_title": "Achievement description"
- },
- "achievement_rarity": {
- "rarity": "Rarity rate",
- "level": "Rarity; 1 means Common; 2 means Uncommon; 3 means Rare; 4 means Ultra Rare"
- },
- "user_achievement_id": "User achievement ID",
- "complete_time": "Time of completion (in miliseconds)",
- "completed_step": "Steps completed",
- "full_completed": "Whether completed; true means yes; false means no"
- }
- ]
- }
-}
-```
-
-### Submit Achievements
-
-Below is the interface for submitting one or more achievements that a player has earned. The achievements submitted will be **added** to the list of achievements the player has already earned.
-
-```sh
-curl -X POST \
- -H "X-TDS-Id: {{clientId}}" \
- -H "X-TDS-Server-Secret: {{serverSecret}}" \
- -H "Content-Type: application/json" \
- -d '{"data": [{"user_id": , "list":
- [{
- "achievement_id": "Achievement ID",
- "achievement_open_id": "Achievement open ID (a custom achievement ID specified by the developer when creating the achievement; this is the unique identifier used when submitting achievement data with the SDK)",
- "complete_time": "Time of completion (in miliseconds)",
- "completed_step": "Steps completed"
- }]
- }]
- }' \
- https://tds-tapsdk.cn.tapapis.com/achievement/open/v1/clients/{{clientId}}/achievements
-```
-
-The response body looks like this:
-
-```json
-{
- "success": true,
- "data": {
- "list": [
- {
- "user_id": "ObjectId",
- "result_list": [
- {
- "result": "Whether this data has been successfully submitted; true means yes; false means no"
- "code": "Is 0 if succeeded and the error code if not"
- "msg": "Is not included if succeeded and the error message if not"
- }
- ]
- }
- ]
- }
-}
-```
-
-### Language Codes
-
-The REST API accepts language codes defined by ISO 639-1. For example, `en` means English and `jp` means Japanese. There are a couple of exceptions:
-
-1. For languages not included in ISO 639-1, the language codes defined in ISO 632-2 are used. For example, `fil` means Filipino.
-2. When a language cannot be represented by a language code, the location code defined in ISO 3166-1 will be attached. For example, `zh_CN` means Simplified Chinese.
-
-The following language codes are supported by the REST API:
-
-| Code | Language |
-| ------- | ----------- |
-| zh_CN | Simplified Chinese |
-| zh_TW | Traditional Chinese |
-| en_US | English (US) |
-| ja_JP | Japanese |
-| ko_KR | Korean |
-| pt_PT | Portuguese |
-| vi_VN | Vietnamese |
-| hi_IN | Hindi |
-| id_ID | Indonesian |
-| ms_MY | Malay |
-| th_TH | Thai |
-| es_ES | Spanish |
-| af | Afrikaans |
-| am | Amharic |
-| bg | Bulgarian |
-| ca | Catalan |
-| hr | Croatian |
-| cs | Czech |
-| da | Danish |
-| nl | Dutch |
-| et | Estonian |
-| fil | Filipino |
-| fi | Finnish |
-| fr | French |
-| de | German |
-| el | Greek |
-| he | Hebrew |
-| hu | Hungarian |
-| is | Icelandic |
-| it | Italian |
-| lv | Latvian |
-| lt | Lithuanian |
-| no | Norwegian |
-| pl | Polish |
-| ro | Romanian |
-| ru | Russian |
-| sr | Serbian |
-| sk | Slovak |
-| sl | Slovenian |
-| sw | Swahili |
-| sv | Swedish |
-| tr | Turkish |
-| uk | Ukrainian |
-| zu | Zulu |
-
-Keep in mind that some of the languages listed above are only supported by the REST API but [not the client SDK](#internationalization).
-
-
-## Video Tutorials
-
-You can refer to the video tutorial:[How to Integrate Achievements in Games](https://www.bilibili.com/video/BV1yH4y1z7F7/) to learn how to access achievements in Untiy projects.
-
-For more video tutorials, see [Developer Academy](https://developer.taptap.cn/tds-tutorials/list). As the SDK features are constantly being improved, there may be inconsistencies between the video tutorials and the new SDK features, so the current documentation should prevail.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/anti-addiction/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/anti-addiction/_category_.json
deleted file mode 100644
index f96b0e4ae..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/anti-addiction/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "防沉迷",
- "collapsed": true,
- "position": 10
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/anti-addiction/faq.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/anti-addiction/faq.mdx
deleted file mode 100644
index e192fbaa1..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/anti-addiction/faq.mdx
+++ /dev/null
@@ -1,86 +0,0 @@
----
-title: Frequently Asked Questions
-sidebar_label: Frequently Asked Questions
-sidebar_position: 3
----
-
-import MultiLang from '/src/docComponents/MultiLang';
-
-### Do individual/group developers need access to anti-addiction tools?
-
-According to the State Press and Publication Administration's [Notice on Further Strict Management to Effectively Prevent Minors from Becoming Addicted to Online Games](https://www.nppa.gov.cn/xxfb/tzgs/202108/t20210830_666285.html), all game publishers and operators are required to implement in-game real name authentication and new anti-addiction strategies.
-For games that do not have a version number, they can access the [Real Name Authentication and Anti-Addiction](/sdk/anti-addiction/features/) launched by TDS.
-
-### What is the relationship between anti-addiction and login?
-
-As of version 3.7.1, Quick Authentication does not rely on the TapTap Login SDK, so anti-addiction and login are not **strongly related**. However, when logged in with TapTap, you can use the [Quick Authentication for anti-addiction](/sdk/anti-addiction/features/#taptap-quick-verification), which allows players to quickly complete the in-game authentication process using their real name information that has been authenticated by the country in TapTap after they have agreed to authorise it.
-In addition, you can also use our [real-name authentication](/sdk/anti-addiction/features/#game-real-name authentication and anti-addiction) feature, which automatically connects to the Ministry of Propaganda's online game anti-addiction real-name authentication system for developers and reports as a developer entity, in line with the Ministry's compliance requirements for game companies to access the system. This is in line with the Ministry's compliance requirements for game companies.
-
-### If you log out of your account and restart the game after passing the real name authentication, the real name authentication process will be skipped.
-
-As of version 3.7.1, Quick Authentication does not rely on the TapTap Login SDK, so there is no **strong correlation** between anti-addiction and login. The first time a player enters the game, a real name authentication popup is triggered, asking the player to authorise the game to obtain TapTap's real name information or to enter their identity information. If the player needs to log in using a different account, they will need to actively log out of their account.
-
-### How do I enable the Real-Name Verification and Anti-addiction service?
-
-You can enable the service on **Developer Centre Backend > Games Services > Development & Build > Compliance Certification**. You can choose one of the following two options for your game:
-
-* **The game has an ISBN**. Once you have completed the prerequisites listed on the page, turn on the service by providing the [parameters obtained from Zhongxuanbu’s system](/sdk/anti-addiction/features/#sign-up-for-zhongxuanbus-real-name-verification-system):
- * Fill in the **bizId, APPID, and Secret Key** obtained from Zhongxuanbu’s system.
- * Then provide the **IP addresses** displayed on the Developer Center to Zhongxuanbu’s system.
-* **The game doesn’t have an ISBN**. You can turn on the service without providing an ISBN. Once your game has obtained an ISBN, you can make a switch by providing the [parameters obtained from Zhongxuanbu’s system](/sdk/anti-addiction/features/#sign-up-for-zhongxuanbus-real-name-verification-system). You won’t need to update the client after you do this.
-
-### What user interfaces does the SDK include?
-
-Most of the interfaces provided by the SDK are used for identity verification. You can take a look at them [here](/sdk/anti-addiction/features/#integrating-tds-real-name-verification-and-anti-addiction-service).
-
-### Authorisation failure
-
-If you see the “Authorisation failure” error when using TapTap Quick Verification, please [configure the signature certificate](/sdk/start/quickstart/#configure-signature-certificate) on the Developer Center.
-
-### No real name authentication configuration was queried
-
-If you see the “No real name authentication configuration was queried” error, it is because the Real-Name Verification service is not enabled. Please enable it on Developer Centre Back Office > Games Services > Development & Build > Compliance.
-
-### userIdentifier is empty
-
-If you see this error, it means that the value you provided for `userIdentifier` is empty. We recommend that your program checks whether this value is empty before submitting it to the server.
-
-### The pop-up for real-name verification is not displayed, and the game is not receiving any callbacks from the SDK.
-
-This often indicates that you have only invoked the code for initializing the UI of the Anti-addiction module and setting up event listeners.
-
-To pull up the pop-up for real-name verification, you’ll **need to invoke** the [interface for identity verification](/sdk/anti-addiction/guide/#identity-verification). After this, you will be able to receive callbacks.
-
-### Verifying Identity Repetitively
-
-We expect that each player only gets verified once and no further pop-ups should show up once the player is already verified. This ensures a better user experience, as the Anti-addiction service can use the result from the previous verification.
-
-If you notice that a player that has already been verified is being verified for a second time, you can try to solve the problem by following the instructions below:
-
-* First of all, make sure that the [`userIdentifier`](#regarding-useridentifier) provided meets the requirements. If the `userIdentifier` of the same player may get changed, the player will be seen as different users and may be asked to verify their identity repetitively. Please make sure that you use the proper value for the `userIdentifier`. We recommend that you use [TDS’ built-in account system](/sdk/taptap-login/guide/start/#quick-log-in-with-taptap) and use each player’s `objectId` as the unique identifier of the player. You can also use the [basic TapTap user verification](/sdk/taptap-login/guide/tap-login/#check-login-status-and-user-info) and use each player’s `openid` or `unionid` as the unique identifier.
-* If your game **already has an ISBN**, please make sure the parameters entered on 开发者中心后台游戏服务 > 开发与构建 > 合规认证 are correct. If the parameters are incorrect, the service will be unable to make requests to Zhongxuanbu’s interface and players may be asked to verify their identity repetitively. Please check the following parameters:
- * All **IP addresses** should be provided to Zhongxuanbu’s system.
- * The **bizId and APPID** should be the ones obtained from Zhongxuanbu’s system.
- * The **Secret Key** should be valid (it is only valid for half a year, so make sure to update it before it expires).
-
-## Caveats
-
-### Regarding `userIdentifier`
-
-When a player opens your game for the first time, the pop-up for identity verification will be displayed, allowing the player to either authorize the game to obtain the identity information from TapTap or manually enter their identity information. Once the verification is done, whenever your game invokes the interface for identity verification with the same `userIdentifier`, the result from the previous verification will be returned and no further pop-ups will be triggered.
-
-Therefore, please make sure that the unique identifier of each user stays unique.
-
-### Environments for Testing Real-Name Verification
-
-No matter if you are building an Android or iOS project, you cannot test the service in Unity Editor’s environment. Please test the service by running packages on real devices or emulators.
-
-
-### In a testing environment for real-name verification, the verification process is still required for test accounts
-
-Please make sure that the **player's unique identifier** `userIdentifier` passed into `startup` is the `unionid` of the TapTap user, which can be obtained by referring to [Quick Log-in With TapTap](/sdk/taptap-login/guide/start/#quick-log-in-with-taptap).
-
-### I see the error "Could not get real name information, please try again later" when using TapTap Quick Authentication
-
-- Verify that the account logged in to the TapTap client has completed real-name verification in the client. As of v3.22.0, TapTap Quick Authentication will not throw an exception if the account logged in to the TapTap client has not completed real-name verification in the TapTap client, but will take the user to the TapTap client for real-name verification.
-- Check that the device time is synchronized with the network. An inaccurate device time will also cause this exception.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/anti-addiction/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/anti-addiction/features.mdx
deleted file mode 100644
index f53f1ac4a..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/anti-addiction/features.mdx
+++ /dev/null
@@ -1,135 +0,0 @@
----
-title: Real-Name Verification and Anti-addiction Introduction
-sidebar_label: Introduction
-sidebar_position: 1
----
-
-## Getting Ready
-:::tip
-Real-name authentication and anti-addiction features are also available for unlicensed games.
-:::
-
-To start using TDS’ Real-Name Verification and Anti-addiction service, please first enable it for your game under **游戏服务 > 开发与构建 > 合规认证**. As shown in the picture, you can choose the option of "已有版号" or "暂无版号", and then click "立即开通".
-
-
-![](https://capacity-files.lcfile.com/8xGphnizlUYCzCxGCgsda1d07119IRqG/compliance-certification.png)
-
-The option "No ISBN" or "With ISBN" is chosen according to the actual situation of the game.
-
-### No ISBN
-
-If the game's ISBN has not yet been applied for, you can choose the "No ISBN" option. After the game has received the ISBN, you can switch to the "With ISBN" option. For the "No ISBN" option, you only need to click the "Open Now" button in the picture above, and the operation will be completed.
-
-### With ISBN
-
-#### Sign Up for Zhongxuanbu’s Real-Name Verification System
-
-Please register your game on [Zhongxuanbu’s anti-addiction and real-name verification system for online games](https://wlc.nppa.gov.cn/fcm_company/index.html#/login?redirect=%2F), complete the interface test, and make sure your game has passed Zhongxuanbu’s review.
-
-You will get the following credentials from the system:
-
-- `bizId`
-- `APPID`
-- `Secret Key`
-
-![](/img/anti-addiction/biz-id.png)
-
-![](/img/anti-addiction/secretkey.png)
-
-The game must have passed the review with Zhongxuanbu.
-
-![](https://capacity-files.lcfile.com/BC7gUR6wfpOh9PHj8XwKybxseeHhl6Fq/anti-examine-success.png)
-
-The game must also pass the interface test with Zhongxuanbu. There are currently 8 test cases. Please note that the IP address whitelist on Zhongxuanbu's **测试接口 > 预置参数 > IP 白名单** should be configured with your game's own IP addresses. The test interface is for the testing purposes only and won't be needed once your game has passed the test.
-
-![](/img/anti-addiction/testcase.png)
-
-Now finish configuring the parameters on the TapTap Developer Center and enter the IP address whitelist provided by the TapTap Developer Center on Zhongxuanbu's website:
-
-![](https://capacity-files.lcfile.com/3WYezwPnXmK3zOLdVGLPWuvuf0MX4m1M/anti-addiction-qualification.png)
-
-## Integrating TDS’ Real-Name Verification and Anti-addiction Service
-
-Once you have finished the steps above, you will be able to start integrating TDS’ Real-Name Verification and Anti-addiction service into your game.
-
-The best practice is: after the user has successfully logged in, the "TapTap Quick Authentication" authorization box pops up. If you click on the "Use" button, you will be taken to the TapTap app to complete the authentication. If you click on the "Don't Use" button, a prompt box will be displayed for you to manually fill in the identity information.
-
-
-:::tip
-The "TapTap Quick Authentication" pop-up box is displayed by default, and the SDK does not support directly popping up the input box for manually filling in real-name identity information. Users have to manually click the "Do not use" button to open up the input box for manually filling in the information.
-:::
-
-
-### TapTap Quick Verification
-
-With TapTap Quick Verification, a player can quickly complete the verification process with the real-name information filed in their TapTap account if they wish to.
-
-Workflow:
-
-![](/img/anti-addiction/anti-addiction-flow.png)
-
-What the player will see:
-
-![](/img/anti-addiction/image2021-10-18_17-57-51.png)
-
-### Real-Name Verification and Anti-addiction
-
-According to the [announcement](https://www.nppa.gov.cn/xxfb/tzgs/202108/t20210830_666285.html) made by the National Press and Publication Administration, all businesses publishing and operating games have to have their games follow the latest policy regarding real-name verification and anti-addiction.
-
-The announcement requires that all users have to sign up and log in to online games with their real and valid identification information. When you use TDS in your game, your game will be connected to Zhongxuanbu’s anti-addiction and real-name verification system and TDS will represent your business to report the information required by the system. This ensures that your business complies with Zhongxuanbu’s legal requirements.
-
-If you don't use TapTap Quick Authentication, that is, after the "TapTap Quick Authentication" authorization box pops up, the "Don't use" button is clicked, then a prompt box will be displayed, and you need to manually fill in the identity information to complete the real-name authentication.
-
-Once the player submits the information, TDS will submit the information to Zhongxuanbu’s system to validate the information. Please make sure you have finished configuring your game on the Developer Center. Otherwise, the validation process won’t work.
-
-![](/img/anti-addiction/anti-addiction-flow-2.png)
-
-What the player will see:
-
-![](/img/anti-addiction/image2021-10-19_17-4-12.png)
-
-## Anti-addiction Policies
-
-The announcement requires that your business strictly limits when a minor can play your game as well as the amount of money they can spend on your game.
-With the interfaces provided by our SDK, your game would be able to check if a minor is currently allowed to play your game, as well as whether they can make a payment of a given amount of money.
-
-![](/img/anti-addiction/not-allow-play.png)
-
-
-### Time Restrictions
-
-Minors under the age of 18 are only allowed to game between **8:00 pm and 9:00 pm on Fridays, Saturdays, Sundays, and public holidays**.
-
-### Payment Restrictions
-
-- Minors under the age of 8 are not allowed to make payments in your game.
-
-Among all the games provided by **the same business**, the following payment restrictions are imposed:
-
-- A minor over the age of 8 but under the age of 16 can make a payment of no more than 50 CNY each time. The total amount they can spend each month is 200 CNY.
-- A minor over the age of 16 but under the age of 18 can make a payment of no more than 100 CNY each time. The total amount they can spend each month is 400 CNY.
-
-Keep in mind that when keeping track of payment restrictions, the amount spent by a minor on all the games provided by the same business will be accumulated.
-For example, if a business has two games, and a minor over the age of 8 but under the age of 16 has already spent 100 CNY in one of the games, they can only spend no more than 100 CNY in the other game.
-
-## Test Accounts
-There are two scenarios for using test accounts:
-
-- The first is when the game is in development and testing, test accounts are needed to test the real-name authentication anti-addiction features.
-- The second is when you are applying for the ISBN.
-
-The test accounts provided by the anti-addiction service are strongly dependent on TapTap Login. 30 test account are provided in accordance with the requirements of the State Press and Publication Administration. The accounts include:
-
-- One group of empty accounts without real-name verification, with a total of 9 accounts;
-- Two groups of minor accounts, 18 in total, each containing 3 each of high, medium, and low ages (above 16 and under 18, above 8 and under 16, and under 8);
-- One group of adult accounts (over 18 years old), with a total of 9 accounts.
-
-According to the usage scenario of test accounts, the usage mode of test accounts is divided into two types: **Regular Mode** and **Test Mode**.
-
-### Regular Mode
-
-The status and attributes of the test accounts cannot be changed in the regular mode, which is applicable to the scenario of submitting the game for review and applying for an ISBN.
-
-### Test Mode
-
-To facilitate developers to test the real-name verification and anti-addiction functions for underage players, the test mode is introduced, under which the status and attributes of the test accounts can be changed. For example, for the underage test accounts provided, you can change whether the user is playable or not at the moment, and the amount of money that has been spent, so that the developer does not need to wait for the time when underage players are allowed to play to test the anti-addiction function. Test accounts can be viewed by going to **TapTap Developer Centre > Your Games > Game Services > Compliance Certification > Test Accounts**.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/anti-addiction/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/anti-addiction/guide.mdx
deleted file mode 100644
index 4837e2de6..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/anti-addiction/guide.mdx
+++ /dev/null
@@ -1,908 +0,0 @@
----
-title: Real-Name Verification and Anti-addiction Guide
-sidebar_label: Guide
-sidebar_position: 2
----
-
-import MultiLang from '/src/docComponents/MultiLang';
-import CodeBlock from '@theme/CodeBlock';
-import sdkVersions from '/src/docComponents/sdkVersions';
-
-:::tip
-Before using the TDS’ Real-Name Verification and Anti-addiction service, please first enable the service on **开发者中心后台 > 游戏服务 > 开发与构建 > 合规认证**. When enabling the service, you can select whether your game has gotten an ISBN.
-:::
-
-## Configure the SDK
-
-You can download the TapSDK from the [Downloads page](/tap-download) and import the anti-addiction module into your game.
-
-
-
-<>
-
-If you are building your game with Unity, you can import a `.unitypackage` file to your game, which includes the iOS module and the Android module. If you are building with other engines or for other platforms, you can import the iOS or Android module natively. Please refer to the documentation for iOS or Android for more information.
-
-**Unity requirement**: You will need Unity 2019.4 or higher to use the SDK.
-
-Supported OS versions:
-
-- Android: 5.0 and higher
-- iOS: 10.0 and higher, Xcode 14.1 or later
-
-The SDK can be imported **either using the Unity Package Manager or manually**.
-
-#### Method 1: Use Unity Package Manager
-
-Add the following dependencies into `Packages/manifest.json`:
-
-
- {`"dependencies":{
- "com.tapsdk.antiaddiction":"https://github.com/taptap/TapAntiAddiction-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.common":"https://github.com/TapTap/TapCommon-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.login":"https://github.com/TapTap/TapLogin-Unity.git#${sdkVersions.taptap.unity}",
-}`}
-
-
-In Unity’s menu bar, select **Window > Package Manager** to view the packages already installed in the project.
-
-#### Method 2: Import Manually
-
-Open the download pages of **TapSDK Unity** and **LeanCloud C# SDK** from the [Downloads](/tap-download) page and download `TapSDK-UnityPackage.zip` and `LeanCloud-SDK-Realtime-Unity.zip` from these pages.
-
-
-
Import the TapTap_Common_{sdkVersions.taptap.unity}.unitypackage and TapTap_AntiAddiction_{sdkVersions.taptap.unity}.unitypackage and TapTap_Login_{sdkVersions.taptap.unity}.unitypackageunzipped from TapSDK-UnityPackage.zip.
-
-
-- If the current project has integrated the Newtonsoft.Json dependency, ignore this step, otherwise download the library file by clicking `Download package` on the right side of the NuGet.org [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json) page and change the downloaded file extension from `.nupkg` to `.zip`, extract the file and copy the internal `Newtonsoft.Json.dll` file into the Plugins directory of the project `Assets - Plugins`. In addition, to avoid deleting the necessary data when exporting the `IL2CPP` platform, create a `link.xml` file in the `Assets` directory (if you already have this file, add the following content) with the following content
-
-```xml
-
-
-
-
-
-```
-
-Configuring for iOS:
-
-Using Xcode 13.0 beta 5 for compiling the project, please follow the steps below to make sure the Xcode project produced by Unity is correctly configured:
-
-1. Go to `Xcode` - `General` - `Frameworks, Libraries, and Embedded Content` and ensure that `AntiAddictionService.framework` and `AntiAddictionUI.framework` are set to `Do Not Embed`.
-2. If the compilation fails due to missing header files or modules, please make sure the path set in `Xcode` - `Build Settings` - `Framework Search Paths` is correct.
-3. Make sure the `Swift Compile Language` / `Swift Language Version` under `Build Settings` of the Xcode project is `Swift5`.
-4. Add `libz.tbd` and `libc++.tbd` as dependencies.
-5. Write your code to integrate the service.
-6. Copy `AntiAddiction-Unity/Assets/Plugins/iOS/Resource/AntiAdictionResources.bundle` to your game project (if `AntiAddictionResources.bundle` is not correctly imported to the Unity project).
-
->
-<>
-
-The SDK supports Android 5.0 (API level 21) and higher. The SDK is compiled with Android Studio.
-
-
-
Copy the Anti-addiction SDKAntiAddiction_{sdkVersions.taptap.android}.aar to the src/main/libs directory under your project
-
Copy the Anti-addiction SDKAntiAddictionUI_{sdkVersions.taptap.android}.aar to the src/main/libs directory under your project
-
Copy TapCommon_{sdkVersions.taptap.android}.aar to the src/main/libs directory under your project
-
Copy TapLogin_{sdkVersions.taptap.android}.aar to the src/main/libs directory under your project
-
-
-Add the following code to `build.gradle` under your project
-
-
-{`repositories{
- flatDir{
- dirs 'src/main/libs'
- }
-}
-dependencies {
- // ...
- implementation(name: "AntiAddiction_${sdkVersions.taptap.ios}", ext: "aar") // Anti-addiction SDK
- implementation(name: "AntiAddictionUI_${sdkVersions.taptap.ios}", ext: "aar") // Anti-addiction SDK
- implementation(name: "TapCommon_${sdkVersions.taptap.ios}", ext: "aar")
- implementation(name: "TapLogin_${sdkVersions.taptap.ios}", ext: "aar")
- // ...
-}`}
-
-
->
-<>
-
-The SDK supports iOS 11 and higher, Xcode 14.1 or later.
-
-Below is the structure of the **Anti-addiction SDK** for iOS:
-
-- `AntiAddictionService`: The base library for the Anti-addiction service written with Swift.
-- `AntiAddictionUI`: The Anti-addiction library with UI, which depends on `AntiAddictionService`. It is written with Objective-C.
-- `AntiAddictionResources.bundle`: Resource files.
-
-Other dependencies:
-
-- `TapCommonSDK.framework`: The base library.
-- `TapLoginSDK.framework`: The base library.
-- `TapCommonResource.bundle`: The Resource files.
-- `TapLoginResource.bundle`: The Resource files.
-
-To add the libraries for the Anti-addiction service:
-
-- Add `AntiAddictionService.framework`, `AntiAddictionUI.framework`, `TapLoginSDK.framework` , and `TapCommonSDK.framework`. Make sure to choose **Do Not Embed** as the embed method when adding these libraries.
-
-- Import the code:
-
- ``` objc
- // AntiAddictionUI
- #import
- ```
-
-Add system dependencies:
-
-Check if the following dependencies have been automatically added to your project:
-
-- `libc++.tdb`
-- `libz.tdb`
-
-If the dependencies cannot be correctly loaded when you try to run your project, you can set the dependencies to be optional and try again.
-
-Configure options for compiling:
-
-- Under *Build Setting*, add `-ObjC` and `-Wl -ld_classic`to *Other Link Flag*.
-
-- Under *Build Setting*, set *Always Embed Swift Standard Libraries* to *YES* so that the Swift standard library will always be imported. This prevents exceptions from happening when you run your app due to missing the Swift standard library. If the library cannot be found in your project, you can create an empty Swift file and Xcode will automatically set up the bridging relationship.
-
-- Under *Build Setting*, select *Swift 5* for *Swift Compiler - Language / Swift Language Version*.
-
->
-
-<>
-
-#### Environment Requirements
-
-* UE 4.26 or higher
-* iOS 12 or higher, Xcode 14.1 or later
-* Android 5.0 (API level 21) or higher
-* macOS 10.14.0 or higher
-* Windows 7 or higher
-
-**Platforms supported**: iOS / Android / Windows / macOS
-
-#### Install Plugins
-
-* Download [TapSDK UE4](/tap-download), unzip `TapSDK-UE4-xxx.zip`, and copy `AntiAddiction`, TapLogin and `TapCommon` to the `Plugins` directory of your project
-* Restart Unreal Editor
-* Go to Edit > Plugins > Project > TapTap and enable the `AntiAddiction` module
-
-#### Add Dependencies
-
-Add the required dependencies to `Project.Build.cs`:
-
-```cs
-PublicDependencyModuleNames.AddRange(new string[] { "Core",
- "CoreUObject",
- "Engine",
- "Json",
- "InputCore",
- "JsonUtilities",
- "SlateCore",
- "TapCommon",
- "TapLogin",
- "AntiAddiction"
-});
-```
-
-#### Import the Header File
-
-```cpp
-#include "TapUEBootstrap.h"
-```
-
-
-
-Compiling a project with a mixture of Objective-C and Swift code
-
-You can take either of the following two methods.
-
-Method one: Replace the Anti-addiction library with the dynamic library
-
-Pros can cons:
-
-* Pros: You don’t have to edit the code of the engine
-* Cons:
- * The size of the package will increase a little bit
- * Only iOS 13 and higher is supported; the project will crash on iOS 12 and below
-
-Steps:
-
-1. Download the [library file](https://github.com/taptap/TapSDK-iOS/releases) that has the same version as the `TapSDK-iOS` you’re using
-2. Replace the `AntiAddictionService.framework` in `Plugins/AntiAddiction/Source/AntiAddiction/ios/framework/AntiAddictionService.zip` with the `Dylib/AntiAddictionService.framework` you just downloaded (unzip -> replace -> zip)
-3. Replace the following part in `AntiAddiction.Build.cs`
-
- ```cs
- new Framework(
- "AntiAddictionService",
- "../AntiAddiction/ios/framework/AntiAddictionService.zip"
- )
- ```
-
- with:
-
- ```cs
- new Framework(
- "AntiAddictionService",
- "../AntiAddiction/ios/framework/AntiAddictionService.zip",
- null,
- true
- )
- ```
-
-4. Compile the project again
-
-Method two: Edit `UnrealBuildTool`
-
-1. Edit `XcodeProject.cs`
-
- Path: `Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Xcode/XcodeProject.cs`
-
- Find the function:
-
- ```cpp
- private void AppendProjectBuildConfiguration(StringBuilder Content, string ConfigName, string ConfigGuid)
- ```
-
- And add the following code to the function:
-
- ```cpp
- // Enable Swift
- Content.Append("\t\t\t\tCLANG_ENABLE_MODULES = YES;" + ProjectFileGenerator.NewLine);
- Content.Append("\t\t\t\tSWIFT_VERSION = 5.0;" + ProjectFileGenerator.NewLine);
- Content.Append("\t\t\t\tLIBRARY_SEARCH_PATHS = \"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\";" + ProjectFileGenerator.NewLine);
- if (ConfigName == "Debug")
- {
- Content.Append("\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";" + ProjectFileGenerator.NewLine);
- }
- Content.Append("\t\t\t\tALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;" + ProjectFileGenerator.NewLine);
- Content.Append("\t\t\t\tEMBEDDED_CONTENT_CONTAINS_SWIFT = YES;" + ProjectFileGenerator.NewLine);
- ```
-
- Your code should look like this:
- ![](https://img.tapimg.com/market/images/e62c405ba77c57475dfaed7b5e550c5b.jpg)
-
-2. Edit `IOSToolChain.cs`
-
- Path: `Engine/Source/Programs/UnrealBuildTool/Platform/IOS/IOSToolChain.cs`
-
- Find the function:
-
- ```cpp
- string GetLinkArguments_Global(LinkEnvironment LinkEnvironment)
- ```
-
- And add the following code to the function:
-
- ```cpp
- // This line shall be placed at the beginning (see the screenshot below)
- // Added by uwellpeng: enable swift support, make sure '/usr/lib/swift' goes before '@executable_path/Frameworks'
- Result += " -rpath \"/usr/lib/swift\"";
-
- // enable swift support
- Result += " -rpath \"@executable_path/Frameworks\"";
- // /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/swift/
- String swiftLibPath = String.Format(" -L {0}Platforms/{1}.platform/Developer/SDKs/{1}{2}.sdk/usr/lib/swift",
- Settings.Value.XcodeDeveloperDir, bIsDevice? Settings.Value.DevicePlatformName : Settings.Value.SimulatorPlatformName, Settings.Value.IOSSDKVersion);
- Result += swiftLibPath;
- Log.TraceInformation("Add swift lib path : {0}", swiftLibPath);
- ///Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos
- swiftLibPath = String.Format(" -L {0}Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/{1}",
- Settings.Value.XcodeDeveloperDir, bIsDevice? Settings.Value.DevicePlatformName.ToLower() : Settings.Value.SimulatorPlatformName.ToLower());
- Result += swiftLibPath;
- Log.TraceInformation("Add swift lib path : {0}", swiftLibPath);
- ///Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/iphoneos
- swiftLibPath = String.Format(" -L {0}Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/{1}",
- Settings.Value.XcodeDeveloperDir, bIsDevice? Settings.Value.DevicePlatformName.ToLower() : Settings.Value.SimulatorPlatformName.ToLower());
- Result += swiftLibPath;
- // Xcode 12 has an additional `swiftcompatabiliy51` library and the following code needs to be added
- if (Settings.Value.IOSSDKVersionFloat >= 14.0f)
- {
- Result += String.Format(" -lswiftCompatibility51");
- }
- ```
-
- Keep in mind that `Result += " -rpath \"/usr/lib/swift\"";` should be placed above `@executable_path/Frameworks`
-
- Your code should look like this:
-
- ![](https://img.tapimg.com/market/images/84ca51379a9f6c7a69262450fbf89b7b.jpg)
-
-3. Recompile UBT
-
- Recompile `UnrealBuildTool` with `msbuild`. You can run the `Terminal` command `msbuild` under the `Engine/Source/Programs/UnrealBuildTool` directory. If the engine directory is under a directory that you cannot edit, you can prepend the command with `sudo`, like `sudo msbuild`.
-
- Once you finish the three steps above, you will be able to compile a project with a mixture of Objective-C and Swift code.
-
-
-
->
-
-
-
-The Anti-addiction SDK needs the permissions for connecting to the internet and sending request data. Please make sure to declare those permissions in your project.
-
-## Initialize the SDK
-
-During this step, you will be initializing the Anti-addiction UI module, providing configurations for starting the anti-addiction function, and setting up listeners for events related to anti-addiction. Keep in mind that **the [Identity Verification](#identity-verification) interface needs to be invoked when the callback is triggered**.
-
-
-<>
-
-```cs
-using TapTap.AntiAddiction;
-using TapTap.AntiAddiction.Model;
-
-AntiAddictionConfig config = new AntiAddictionConfig()
-{
- gameId = "your_client_id", // The Client ID obtained from the TapTap Developer Center
- useAgeRange = false , // Whether to use age group information
- showSwitchAccount = false, // Whether to display the button for switching account
-};
-
-Action callback = (code, errorMsg) => {
- // code == 500; // Logged in
- // code == 1000; // Logged out
- // code == 1001; // The user tapped the button for switching account
- // code == 1030; // The user is not allowed to play games at this time
- // code == 1050; // The time limit is reached
- // code == 1100; // The user is unable to access the game due to triggering the age limit set by the app
- // code == 1200; // If the data request fails, the game needs to check whether the currently set application information is correct and judge whether the current network connection is normal.
- // code == 9002; // The user closed the window for real-name verification
- UnityEngine.Debug.LogFormat($"code: {code} error Message: {errorMsg}");
-};
-
-AntiAddictionUIKit.Init(config);
-AntiAddictionUIKit.SetAntiAddictionCallback(callback);
-```
-
->
-<>
-
-
-```java
-// The first argument passed into the interfaces of the Android SDK is always the current `Activity`
-
-Config config = new Config.Builder()
- .withClientId("your_client_id") // The Client ID obtained from the TapTap Developer Center
- .useAgeRange(false) // Whether to use age group information
- .showSwitchAccount(false) // Whether to display the button for switching account
- .build();
-
-AntiAddictionUIKit.init(activity, config, new AntiAddictionUICallback() {
- @Override
- public void onCallback(int code, Map extras) {
- if (code == Constants.ANTI_ADDICTION_CALLBACK_CODE.LOGIN_SUCCESS){
- Log.d("TapTap-AntiAddiction", "The player is qualified to play the game");
- }
- }
-});
-```
-
->
-<>
-
-```objc
-AntiAddictionConfig *config = [[AntiAddictionConfig alloc] init];
-config.clientID = @"your_client_id"; // The Client ID obtained from the TapTap Developer Center
-config.useAgeRange = NO; // Whether to use age group information
-config.showSwitchAccount = NO; // Whether to display the button for switching account
-// `self` needs to implement the `AntiAddictionDelegate` protocol; `self` can be replaced by another object that follows the `AntiAddictionDelegate` protocol
-[AntiAddiction initWithConfig:config];
-[AntiAddiction setDelegate:delegate];
-```
-
-The `delegate` object of `+[AntiAddiction initWithConfig:delegate:]` needs to implement the following protocol to receive callbacks:
-
-```objc
-- (void)antiAddictionCallbackWithCode:(AntiAddictionResultHandlerCode)code extra:(NSString * _Nullable)extra {
- // Anti-addiction callback
-}
-```
-
->
-
-<>
-
-```cpp
-FAAUConfig Config;
-Config.ClientID = TEXT("your_client_id"); // The Client ID obtained from the TapTap Developer Center
-Config.ShowSwitchAccount = false; // Whether to display the button for switching account
-Config.UseAgeRange = false; // Whether to use age group information
-AntiAddictionUE::Init(Config);
-```
-
-Once the Anti-addiction service is started, there will be callbacks for different events being triggered, so the `AntiAddictionUE::OnCallBack` callback needs to be listened on. See the values of `AntiAddictionUE::ResultHandlerCode` for a list of possible events.
-
-```cpp
-// Bind the `AntiAddictionUE::OnCallBack` callback
-AntiAddictionUE::OnCallBack.BindUObject(this, &UAntiAddictionWidget::OnCallBack);
-
-void UAntiAddictionWidget::OnCallBack(AntiAddictionUE::ResultHandlerCode Code, const FString& Message) {
- TUDebuger::DisplayShow(FString::Printf(TEXT("Code: %d, Message: %s"), Code, *Message));
- switch (Code) {
- case AntiAddictionUE::LoginSuccess:
- TUDebuger::DisplayShow(TEXT("Logged in"));
- break;
- case AntiAddictionUE::Exited:
- TUDebuger::DisplayShow(TEXT("Logged out"));
- break;
- case AntiAddictionUE::SwitchAccount:
- TUDebuger::DisplayShow(TEXT("The user tapped the button for switching account"));
- break;
- case AntiAddictionUE::DurationLimit:
- TUDebuger::DisplayShow(TEXT("The time limit is reached"));
- break;
- case AntiAddictionUE::PeriodRestrict:
- TUDebuger::DisplayShow(TEXT("The user is not allowed to play games at this time"));
- break;
- case AntiAddictionUE::RealNameStop:
- TUDebuger::DisplayShow(TEXT("The user closed the window for real-name verification"));
- break;
- default:
- TUDebuger::WarningLog(TEXT("Unknown Code: ") + FString::FromInt(Code));
- break;
- }
-}
-```
-
->
-
-
-
-### Parameters
-
-
-
-<>
-
-- `config` is the configuration for the Anti-addiction service. It contains the following parameters:
- - `gameId`: The `Client ID` of the game, which can be found on the Developer Center (**开发者中心 > 你的游戏 > 游戏服务 > 应用配置**).
-
- - `useTapLogin`: Whether to use [TapTap Quick Verification](#taptap-quick-verification). We recommend that you enable this feature so that players who have already confirmed their identities on TapTap can quickly complete the verification process for anti-addiction.
-
- - `showSwitchAccount`: Whether to display the button for switching accounts. If your game doesn’t provide the function for players to switch accounts, you can make this button hidden from the user interface. If you choose to have the button displayed (as shown in the image below), the `1001` callback will be triggered when the player hits the button. Your game can handle the player’s request for switching accounts upon receiving this code.
-
-- `callback` is the callback interface provided by the SDK to notify users of events. It contains the following parameters:
- - `code`: The callback type for the event. See [Callback Types](#callback-types) for more information.
-
- - `errorMsg`: If an error occurs, this will be the error message.
-
->
-
-<>
-
-The following parameters need to be provided when you initialize the Anti-addiction service:
-
-- The `Client ID` of the game, which can be found on the Developer Center (**开发者中心 > 你的游戏 > 游戏服务 > 应用配置**).
-- Whether to use [TapTap Quick Verification](#taptap-quick-verification). We recommend that you enable this feature so that players who have already confirmed their identities on TapTap can quickly complete the verification process for anti-addiction.
-- Whether to display the button for switching accounts. If your game doesn’t provide the function for players to switch accounts, you can make this button hidden from the user interface. If you choose to have the button displayed (as shown in the image below), the `1001` callback will be triggered when the player hits the button. Your game can handle the player’s request for switching accounts upon receiving this code.
-
->
-
-<>
-
-- `config` is the configuration for the Anti-addiction service. It contains the following parameters:
- - `clientID`: The `Client ID` of the game, which can be found on the Developer Center (**开发者中心 > 你的游戏 > 游戏服务 > 应用配置**).
-
- - `useTapLogin`: Whether to use [TapTap Quick Verification](#taptap-quick-verification). We recommend that you enable this feature so that players who have already confirmed their identities on TapTap can quickly complete the verification process for anti-addiction.
-
- - `showSwitchAccount`: Whether to display the button for switching accounts. If your game doesn’t provide the function for players to switch accounts, you can make this button hidden from the user interface. If you choose to have the button displayed (as shown in the image below), the `1001` callback will be triggered when the player hits the button. Your game can handle the player’s request for switching accounts upon receiving this code.
-
-- The `delegate` object needs to implement the `-[AntiAddictionDelegate antiAddictionCallbackWithCode:extra:]` protocol to receive callbacks
- - `code`: The callback type for the event. See [Callback Types](#callback-types) for more information.
-
- - `extra`: Additional information for the current event.
-
->
-
-<>
-
-- `config` is the configuration for the Anti-addiction service. It contains the following parameters:
- - `ClientID`: The `Client ID` of the game, which can be found on the Developer Center (**开发者中心 > 你的游戏 > 游戏服务 > 应用配置**).
-
- - `UseTapLogin`: Whether to use [TapTap Quick Verification](#taptap-quick-verification). We recommend that you enable this feature so that players who have already confirmed their identities on TapTap can quickly complete the verification process for anti-addiction.
-
- - `ShowSwitchAccount`: Whether to display the button for switching accounts. If your game doesn’t provide the function for players to switch accounts, you can make this button hidden from the user interface. If you choose to have the button displayed (as shown in the image below), the `1001` callback will be triggered when the player hits the button. Your game can handle the player’s request for switching accounts upon receiving this code.
-
-
->
-
-
-
-![The user interface for switching account](/img/anti-addiction/switch-account.png)
-
-### Callback Types
-
-| Callback code | Callback type | When does it get triggered |
-|---|---|---|
-| 500 | `LOGIN_SUCCESS` | Logged in |
-| 1000 | `EXITED` | Logged out |
-| 1001 | `SWITCH_ACCOUNT` | The user tapped the button for switching account |
-| 1030 | `PERIOD_RESTRICT` | The user is not allowed to play games at this time |
-| 1050 | `DURATION_LIMIT` | The time limit is reached |
-| 1100 | `AGE_LIMIT` | The user is unable to access the game due to triggering the age limit set by the app |
-| 1200 | `INVALID_CLIENT_OR_NETWORK_ERROR` | If the data request fails, the game needs to check whether the currently set application information is correct and judge whether the current network connection is normal. |
-| 9002 | `REAL_NAME_STOP` | The user closed the window for real-name verification |
-
-## Identity Verification
-
-The SDK provides two methods for you to verify the identities of players:
-
-1. TapTap Quick Verification: This allows players to authorize your game to use their age range information from TapTap to quickly complete the real-name verification process.
-2. Manual entry: This lets players enter their identity information with the user interface provided by the SDK. In this case, the "TapTap Quick Authentication" box pops up and the "Don't use" button is clicked.
-
-TDS’ server will tell if a player can play your game based on their identity information. The player’s information will be automatically sent to Zhongxuanbu’s system.
-
-We recommend that you adopt the first method so that players won’t have to manually enter their identity information and can start playing your game more quickly.
-
-### TapTap Quick Verification
-
-:::info
-**Please make sure you have completed the following steps**:
-
-- **Enable TapTap Login**. If this step is not completed, the player will see “应用未开通 TapTap 登录服务” when using TapTap Quick Verification.
-- **[Configure the signature certificate](/sdk/start/quickstart/#configure-signature-certificate) on the Developer Center**. If this step is not completed, the player will see “授权失败” when using TapTap Quick Verification.
-:::
-
-To enable TapTap Quick Verification, set the parameter for TapTap Quick Verification to `true` when initializing the SDK.
-
-To start the verification process, you need to provide `userIdentifier`, the unique identifier of the player. The SDK will then open the TapTap app. If the TapTap app is not installed on the device, a WebView will be opened. The player can then authorize the game to use their age range information from TapTap to complete the real-name verification process in the game.
-
-For `userIdentifier`, the **unique identifier of the player**, if your game is using [TDS’ built-in account system](/sdk/taptap-login/guide/start/#quick-log-in-with-taptap), you can use the player’s `objectId` as it; if your game is using the [basic TapTap user verification](/sdk/taptap-login/guide/tap-login/#check-login-status-and-user-info), you can use the player’s `openid` or `unionid`.
-
-
-
-<>
-
-```cs
-// The unique identifier cannot be longer than 64 characters
-string userIdentifier = "THE_UNIQUE_IDENTIFIER_OF_THE_PLAYER";
-AntiAddictionUIKit.StartupWithTapTap(userIdentifier);
-```
-
-**Attention**: If your Unity project hasn’t integrated the TapTap Login module, you’ll need to configure the `URLSchema` when creating a package for iOS from the exported Xcode project.
-
-
-How to configure the `URLSchema` for an Xcode project
-
-Open `info.plist` and add the following configurations (replace the `clientID` with the Client ID you obtained from the Developer Center):
-
-![](https://capacity-files.lcfile.com/xLmohBqQCMvHMpvxps7pReT9kI8CjSGj/tap_ios_info.png)
-
- ```xml
- CFBundleURLTypes
-
-
- CFBundleTypeRole
- Editor
- CFBundleURLName
- taptap
- CFBundleURLSchemes
-
-
- tt[clientID]
-
-
-
-
- LSApplicationQueriesSchemes
-
- tapiosdk
- tapsdk
- taptap
-
- ```
-
->
-<>
-
-```java
-// The unique identifier cannot be longer than 64 characters
-String userIdentifier = "THE_UNIQUE_IDENTIFIER_OF_THE_PLAYER";
-AntiAddictionUIKit.startupWithTapTap(activity, userIdentifier);
-```
-
->
-<>
-
-```objc
-// The unique identifier cannot be longer than 64 characters
-NSString *userIdentifier = @"THE_UNIQUE_IDENTIFIER_OF_THE_PLAYER";
-[AntiAddiction startupWithTapTap:userIdentifier];
-```
-
-To use **TapTap Quick Verification**, the callback needs to be embedded into `AppDelegate`.
-
-Attention: If you have already added this for the TapTap Login module, you can skip this step.
-
-```objc
-#import
-
-// The new callback
-- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
- return [TDSHandleUrl handleOpenURL:url];
-}
-```
-
-If you haven’t integrated the TapTap Login module, you will need to configure the `URLSchema` as well.
-
-Open `info.plist` and add the following configurations (replace the `clientID` with the Client ID you obtained from the Developer Center):
-
-![](https://capacity-files.lcfile.com/xLmohBqQCMvHMpvxps7pReT9kI8CjSGj/tap_ios_info.png)
-
- ```xml
- CFBundleURLTypes
-
-
- CFBundleTypeRole
- Editor
- CFBundleURLName
- taptap
- CFBundleURLSchemes
-
-
- tt[clientID]
-
-
-
-
- LSApplicationQueriesSchemes
-
- tapiosdk
- tapsdk
- taptap
-
- ```
-
->
-
-<>
-
-```cpp
-AntiAddictionUE::StartupWithTapTap(TEXT("your_userIdentifier"));
-```
-
->
-
-
-
-### Manual Entry
-
-If you prefer not to enable TapTap Quick Verification, set the parameter for TapTap Quick Verification to `false` when initializing the SDK.
-
-The format of the `userIdentifier`, the **unique identifier of the player**, will be defined by your game. It should be no longer than 64 characters.
-
-## Log Out
-
-When a user logs out of your game, you can invoke this method to reset the states of the Anti-addiction service.
-
-
-
-```cs
-AntiAddictionUIKit.Exit();
-```
-
-```java
-AntiAddictionUIKit.exit();
-```
-
-```objc
-[AntiAddiction exit];
-```
-
-```cpp
-AntiAddictionUE::Exit();
-```
-
-
-
-## Obtain Player’s Age Range
-
-Invoke the following interface to get the player’s age range:
-
-
-
-```cs
-int ageRange = AntiAddictionUIKit.AgeRange;
-```
-
-```java
-int ageRange = AntiAddictionUIKit.getAgeRange();
-```
-
-```objc
-NSInteger ageRange = [AntiAddiction getAgeRange];
-```
-
-```cpp
-EAAUAgeLimit AgeLimit = AntiAddictionUE::GetAgeRange();
-```
-
-
-
-The `ageRange` in the example above is an integer indicating the minimum age among the age range of the player.
-If its value is `-1`, it means that the age range of the user is unknown. This indicates that the user hasn’t verified their identity yet.
-
-| Value | Meaning |
-| - | - |
-| -1 | Unknown |
-| 0 | 0 to 7 years old |
-| 8 | 8 to 15 years old |
-| 16 | 16 to 17 years old |
-| 18 | Adult |
-
-## Get Remaining Time (In Seconds)
-
-To get the time remaining for the player:
-
-
-
-```cs
-int remainingTimeInSeconds = AntiAddictionUIKit.RemainingTime;
-```
-
-```java
-int remainingTimeInSeconds = AntiAddictionUIKit.getRemainingTime();
-```
-
-```objc
-NSInteger remainingTimeInSeconds = [AntiAddiction getRemainingTime];
-```
-
-```cpp
-int RemainingTime = AntiAddictionUE::GetRemainingTime();
-```
-
-
-
-## Get Remaining Time (In Minutes)
-
-To get the time remaining for the player:
-
-
-
-```cs
-int remainingTimeInMinutes = AntiAddictionUIKit.RemainingTimeInMinutes;
-```
-
-```java
-int remainingTimeInMinutes = AntiAddictionUIKit.getRemainingTimeInMinutes();
-```
-
-```objc
-NSInteger remainingTimeInMinutes = [AntiAddiction getRemainingTimeInMinutes];
-```
-
-```cpp
-int RemainingTimeInMinutes = AntiAddictionUE::GetRemainingTimeInMinutes();
-```
-
-
-
-## Check Payment Restrictions
-
-Different payment restrictions are imposed on minors that fall under different age ranges.
-To enable payment restrictions, you will be checking if restrictions exist before a minor attempts to make a payment. When a payment goes through, you will also be reporting the amount paid to our service.
-
-To check if the current player is allowed to make a payment when your game receives a request from the player to make a payment:
-
-
-
-```cs
-long amount = 100;
-AntiAddictionUIKit.CheckPayLimit(amount,
- (result) => {
- // If `status` is `1`, the player is allowed to make a payment
- int status = result.status;
- if (status == 1) {
- // The player is allowed to make a payment
- }
- },
- (exception) => {
- // Handle exception
- }
-);
-```
-
-```java
-long amount = 100;
-AntiAddictionUIKit.checkPayLimit(activity, amount,
- new Callback() {
- @Override
- public void onSuccess(CheckPayResult result) {
- // If `status` is `true`, the player is allowed to make a payment; otherwise, the player cannot make a payment
- if (result.status) {
- }
- }
-
- @Override
- public void onError(Throwable throwable) {
- // Handle exception
- }
- }
-);
-```
-
-```objc
-NSInteger amount = 100;
-[AntiAddiction checkPayLimit:amount resultBlock:^(BOOL status) {
- if (status) {
- // Not restricted
- }
-} failureHandler:^(NSString * _Nonnull error) {
- // Handle exception
-}];
-```
-
-```cpp
-AntiAddictionUE::CheckPayLimit(FCString::Atoi(*AmountTF->Text.ToString()), [](bool Status) {
- TUDebuger::DisplayShow(FString::Printf(TEXT("Status: %d"), Status));
-}, [](const FString& Msg) {
- TUDebuger::ErrorShow(Msg);
-});
-```
-
-
-
-The unit of the amount is *cent*.
-
-To make the function for checking payment restrictions work, your game should be reporting all the payments made by minor players.
-We recommend that you report payments on the server side. Refer to [the description of the relative REST API](#report-payment-amounts) for more information on how you can report payments on the server side.
-You can also report payments using the interface provided by the SDK when a minor player successfully makes a payment. However, this is a less reliable method compared to the former one and is often used by games that don’t require connecting to servers.
-
-
-
-```cs
-long amount = 100;
-AntiAddictionUIKit.SubmitPayResult(amount,
- () => {
- // Succeeded
- }, (exception) => {
- // Handle exception
- }
-);
-```
-
-```java
-long amount = 100;
-AntiAddictionUIKit.submitPayResult(amount,
- new Callback() {
- @Override
- public void onSuccess(SubmitPayResult result) {
- // Succeeded
- }
-
- @Override
- public void onError(Throwable throwable) {
- // Handle exception
- }
- }
-);
-```
-
-```objc
-NSInteger amount = 100;
-[AntiAddiction submitPayResult:amount callBack:^(BOOL success) {
- if (success) {
- // Succeeded
- }
-} failureHandler:^(NSString * _Nonnull error) {
- // Handle exception
-}];
-```
-
-```cpp
-AntiAddictionUE::SubmitPayResult(FCString::Atoi(*AmountTF->Text.ToString()), [](bool Success) {
- TUDebuger::DisplayShow(FString::Printf(TEXT("Success: %d"), Success));
-}, [](const FString& Msg) {
- TUDebuger::ErrorShow(Msg);
-});
-```
-
-
-
-When reporting payment amount, the unit of the amount is also *cent*.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/authentication/faq.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/authentication/faq.mdx
index 85ab44116..8110d8eea 100644
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/authentication/faq.mdx
+++ b/i18n/en/docusaurus-plugin-content-docs/current/sdk/authentication/faq.mdx
@@ -1,13 +1,23 @@
---
-title: FAQ
-sidebar_label: FAQ
-sidebar_position: 5
+title: 内建账户常见问题
+sidebar_label: 常见问题
+sidebar_position: 4
---
-### The built-in account username is inconsistent with the TapTap account username
+### 应用内用户的密码需要加密吗
-Currently, TapTap nicknames and game nicknames are separate. Our design logic is that after using TapTap login for the first time, the login information will be stored in the built-in account. However, if you modify your TapTap nickname later, the original nickname stored in the built-in account will not be updated. If you want to use the updated nickname, you can try adding a [change nickname](/sdk/authentication/guide/#setting-other-user-properties) feature in the game. After a user provides their new nickname, the client calls the SDK method for setting other user attributes to update the nickname.
+不需要加密密码,我们的服务端已使用随机生成的 salt,自动对密码做了加密。 如果用户忘记了密码,可以调用 `requestResetPassword` 方法(具体查看 SDK 的 AVUser 用法),向用户注册的邮箱发送邮件,用户以此可自行重设密码。 在整个过程中,密码都不会有明文保存的问题,密码也不会在客户端保存,只是会保存 sessionToken 来标示用户的登录状态。
-### 403 (Forbidden) error after changing password for built-in accounts
+### sessionToken 在什么情况下会失效?
-If you have checked "Force client to re-login after password change" in Developer Center > Your Game > Game Services > Cloud Services > Built-in Accounts > Settings, then when a user changes their password, their session token will be reset. At this point, you need to prompt the user to log in again; otherwise, they will encounter a 403 (Forbidden) error.
\ No newline at end of file
+如果在控制台的存储的设置中勾选了「密码修改后,强制客户端重新登录」,则用户修改密码后, sessionToken 会变更,需要重新登录。如果没有勾选这个选项,Token 就不会改变。当新建应用时,这个选项默认是被勾上的。
+
+### 在 PC 端用手机号登录,在小程序上用微信登录,如何绑定到同一个账号上?
+
+从逻辑上,在 PC 端登录的账号,与在小程序中用微信登录的账号,他们没有任何可以联系在一起的地方。如果都是独立创建了两个账号,只能在业务层面进行绑定(也就是将一个账号的所有关联对象全都迁移到另一个账号,然后删除原账号)。
+
+如果可以在业务上加一些限制,则可以避免上面这种「创建了两个独立的账号」的情况。比如,如果手机号是账号必须设置的信息,那么我们可以在以手机号作为关联项。具体的步骤如下,首先是 `loginWithWeapp` 并带上 `failOnNotExist` 参数,这样如果该微信关联的用户已经存在则照常登录,如果没有则会失败,此时跳转到使用手机号登录/注册的页面,让用户通过手机号登录或注册,成功之后再通过 `associateWithWeapp` 接口关联当前微信账号。
+
+### 不通过短信验证能否强制修改 _User 表 mobilePhoneVerified 字段,使其设置为 true?
+
+可以通过云引擎使用 [master key](/sdk/engine/functions/sdk/#使用超级权限) 来修改 `mobilePhoneVerified` 的值。因为云引擎运行在可信的服务器端环境中,所以可以全局开启超级权限(Master Key),这样云端会跳过包括 ACL 和 Class 权限在内的检查,让你自由地操作所有云存储中的数据,当然这种方式也只允许调用一些仅供 Master Key 使用的 API。
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/authentication/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/authentication/features.mdx
deleted file mode 100644
index 318949be8..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/authentication/features.mdx
+++ /dev/null
@@ -1,56 +0,0 @@
----
-title: TDS Authentication Introduction
-sidebar_label: Introduction
-sidebar_position: 1
----
-
-import { Conditional } from "/src/docComponents/conditional";
-
-## Introduction
-
-It’s a common practice for games to have an account system that can store players’ data on the cloud. With TDS Authentication, you can easily add a robust and secure account system in your game that allows players to sign in with services like TapTap, Apple, WeChat, QQFacebook, or even as a guest. Using such a system doesn’t require the knowledge of implementing the backend of an account system. You can simply call the APIs provided by the service and utilize all the account-related features by accessing the TDS User object.
-
-## Use Cases
-
-- If you don’t have an account system in your game yet, you can quickly build one with TDS Authentication.
-- If you already have an account system built on your own, you can still use TDS Authentication for accessing other TDS services (including Data Storage, Friends, and Achievements).
-
-### Sign-in Methods
-
-- Guest account
-- OAuth with TapTap, Apple, WeChat, QQFacebook, etc.
-
-## Using the Service
-
-1. Add TapSDK into your game.
-2. Call the APIs provided by the SDK to access the TDS User object.
-3. Manage user accounts on the TapTap Developer Center.
-
-### TDS User
-
-To access the account system on TDS, the client needs to provide a unique identifier of the player that gets linked to a TDS User on the cloud so that the player can access other TDS services. It’s up to you to decide the form of the identifier.
-
-### Guest Accounts
-
-You may allow players to sign in as a guest so that they can access TDS services without having to link an account.
-
-### Third-Party Sign-on
-
-To allow players to log in with services like TapTap, Apple, WeChat, and QQand Facebook, you have to obtain an OAuth token from the service provider. If you want to use TapTap Login, please first complete the application process to enable TapTap Login. You can then access the account system on TDS with the OAuth token obtained from TapTap Login.
-
-## Validating Access Tokens
-
-When using third-party sign-on, TDS Authentication can help you validate access tokens with service providers to improve the security of the account system of your game. You can enable this feature on **TapTap Developer Center > Game Services > TDS Authentication**.
-
-![Third-party websites](https://capacity-files.lcfile.com/hcF5qI41k0MVh7xxVQTjQiJtHiYUopuS/io-tdsuser-oauth-providers.png)
-
-
-## Admin Console
-
-You can manage user accounts by going to the TapTap Developer Center.
-
-![Managing users](https://capacity-files.lcfile.com/JlrSbqS0DcEHQAWTgMRjoOGR9sj2qBsV/io-lc-users-console.png)
-
-## TDS Services That Depend on TDS Authentication
-
-All the cloud services provided by TDS require you to use TDS Authentication. You can browse those services on the Developer Center.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/authentication/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/authentication/guide.mdx
index 7b321a45a..865d680d3 100644
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/authentication/guide.mdx
+++ b/i18n/en/docusaurus-plugin-content-docs/current/sdk/authentication/guide.mdx
@@ -1,342 +1,1878 @@
---
-title: TDS Authentication Guide
-sidebar_label: Guide
+title: 内建账户指南
+sidebar_label: 开发指南
sidebar_position: 2
---
import MultiLang from "/src/docComponents/MultiLang";
import { Conditional } from "/src/docComponents/conditional";
-Starting from TapSDK 3.0, there will be a built-in account system for you to use in your game. You can generate user accounts (`TDSUser`) in your game with the results of TapTap OAuth. You can also link the authentication results of third-party platforms to this account.
-The Friends and Achievements services provided by the TapSDK also depend on this account system.
+用户系统几乎是每款应用都要加入的功能,我们为此专门提供了一个 `LCUser` 类来方便应用使用各项用户管理的功能。
-## Initialization
+`LCUser` 是 `LCObject` 的子类,这意味着任何 `LCObject` 提供的方法也适用于 `LCUser`,唯一的区别就是 `LCUser` 提供一些额外的用户管理相关的功能。
-See [TapSDK Quickstart](/sdk/start/quickstart/) for how to initialize the SDK.
+## 用户的属性
-## `TDSUser` and `LCUser`
+`LCUser` 相比一个普通的 `LCObject` 多出了以下属性:
-`TDSUser` is inherited from the `LCUser` class.
-`LCUser` is the account system provided by LeanCloud, and `TDSUser` basically inherited all the interfaces provided by `LCUser`. `TDSUser` includes some minor adjustments we made on functionalities and interfaces to fulfill the needs of TDS, so we recommend that you implement the account system in your game with `TDSUser`.
+- `username`:用户的用户名。
+- `password`:用户的密码。
+- `email`:用户的电子邮箱。
+- `emailVerified`:用户的电子邮箱是否已验证。
+- `mobilePhoneNumber`:用户的手机号。
+- `mobilePhoneVerified`:用户的手机号是否已验证。
-## TapTap Login
+在接下来对用户功能的介绍中我们会逐一了解到这些属性。
-See [Integrate TapTap Login](/sdk/taptap-login/guide/start/) for more details.
+## 注册
-## Guest Login
+用户第一次打开应用的时候,可以让用户注册一个账户。下面的代码展示了一个典型的使用用户名和密码注册的流程:
-To create a guest account in the account system:
+
+
+<>
+
+```cs
+// 创建实例
+LCUser user = new LCUser();
+
+// 等同于 user["username"] = "Tom";
+user.Username = "Tom";
+user.Password = "cat!@#123";
+
+// 可选
+user.Email = "tom@xd.com";
+user.Mobile = "+8619201680101";
+
+// 设置其他属性的方法跟 LCObject 一样
+user["gender"] = "secret";
+await user.SignUp();
+```
+
+新建 `LCUser` 的操作应使用 `SignUp` 而不是 `Save`,但以后的更新操作就可以用 `Save` 了。
+
+>
+
+<>
+
+```java
+// 创建实例
+LCUser user = new LCUser();
+
+// 等同于 user.put("username", "Tom")
+user.setUsername("Tom");
+user.setPassword("cat!@#123");
+
+// 可选
+user.setEmail("tom@xd.com");
+user.setMobilePhoneNumber("+8619201680101");
+
+// 设置其他属性的方法跟 LCObject 一样
+user.put("gender", "secret");
+
+user.signUpInBackground().subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCUser user) {
+ // 注册成功
+ System.out.println("注册成功。objectId:" + user.getObjectId());
+ }
+ public void onError(Throwable throwable) {
+ // 注册失败(通常是因为用户名已被使用)
+ }
+ public void onComplete() {}
+});
+```
+
+新建 `LCUser` 的操作应使用 `signUpInBackground` 而不是 `saveInBackground`,但以后的更新操作就可以用 `saveInBackground` 了。
+
+>
+
+<>
+
+```objc
+// 创建实例
+LCUser *user = [LCUser user];
+
+// 等同于 [user setObject:@"Tom" forKey:@"username"]
+user.username = @"Tom";
+user.password = @"cat!@#123";
+
+// 可选
+user.email = @"tom@xd.com";
+user.mobilePhoneNumber = @"+8619201680101";
+
+// 设置其他属性的方法跟 LCObject 一样
+[user setObject:@"secret" forKey:@"gender"];
+
+[user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
+ if (succeeded) {
+ // 注册成功
+ NSLog(@"注册成功。objectId:%@", user.objectId);
+ } else {
+ // 注册失败(通常是因为用户名已被使用)
+ }
+}];
+```
+
+新建 `LCUser` 的操作应使用 `signUpInBackground` 而不是 `saveInBackground`,但以后的更新操作就可以用 `saveInBackground` 了。
+
+>
+
+<>
+
+```swift
+do {
+ // 创建实例
+ let user = LCUser()
+
+ // 等同于 user.set("username", value: "Tom")
+ user.username = LCString("Tom")
+ user.password = LCString("cat!@#123")
+
+ // 可选
+ user.email = LCString("tom@xd.com")
+ user.mobilePhoneNumber = LCString("+8619201680101")
+
+ // 设置其他属性的方法跟 LCObject 一样
+ try user.set("gender", value: "secret")
+
+ _ = user.signUp { (result) in
+ switch result {
+ case .success:
+ break
+ case .failure(error: let error):
+ print(error)
+ }
+ }
+} catch {
+ print(error)
+}
+```
+
+新建 `LCUser` 的操作应使用 `signUp` 而不是 `save`,但以后的更新操作就可以用 `save` 了。
+
+>
+
+<>
+
+```dart
+// 创建实例
+LCUser user = LCUser();
+
+// 等同于 user['username'] = 'Tom';
+user.username = 'Tom';
+user.password = 'cat!@#123';
+
+// 可选
+user.email = 'tom@xd.com';
+user.mobile = '+8619201680101';
+
+// 设置其他属性的方法跟 LCObject 一样
+user['gender'] = 'secret';
+await user.signUp();
+```
+
+新建 `LCUser` 的操作应使用 `signUp` 而不是 `save`,但以后的更新操作就可以用 `save` 了。
+
+>
+
+<>
+
+```js
+// 创建实例
+const user = new AV.User();
+
+// 等同于 user.set('username', 'Tom')
+user.setUsername("Tom");
+user.setPassword("cat!@#123");
+
+// 可选
+user.setEmail("tom@xd.com");
+user.setMobilePhoneNumber("+8619201680101");
+
+// 设置其他属性的方法跟 AV.Object 一样
+user.set("gender", "secret");
+
+user.signUp().then(
+ (user) => {
+ // 注册成功
+ console.log(`注册成功。objectId:${user.id}`);
+ },
+ (error) => {
+ // 注册失败(通常是因为用户名已被使用)
+ }
+);
+```
+
+新建 `AV.User` 的操作应使用 `signUp` 而不是 `save`,但以后的更新操作就可以用 `save` 了。
+
+>
+
+<>
+
+```python
+# 创建实例
+user = leancloud.User()
+
+# 等同于 user.set('username', 'Tom')
+user.set_username('Tom')
+user.set_password('cat!@#123')
+
+# 可选
+user.set_email('tom@xd.com')
+user.set_mobile_phone_number('+8619201680101')
+
+# 设置其他属性的方法跟 leancloud.Object 一样
+user.set('gender', 'secret')
+
+user.sign_up()
+```
+
+新建 `leancloud.User` 的操作应使用 `sign_up` 而不是 `save`,但以后的更新操作就可以用 `save` 了。
+
+>
+
+<>
+
+```php
+// 创建实例
+$user = new User();
+
+// 等同于 $user->set("username", "Tom")
+$user->setUsername("Tom");
+$user->setPassword("cat!@#123");
+
+// 可选
+$user->setEmail("tom@xd.com");
+$user->setMobilePhoneNumber("+8619201680101");
+
+// 设置其他属性的方法跟 LeanObject 一样
+$user->set("gender", "secret");
+
+$user->signUp();
+```
+
+新建 `User` 的操作应使用 `signUp` 而不是 `save`,但以后的更新操作就可以用 `save` 了。
+
+>
+
+```go
+// 注册用户
+user, err := client.Users.SignUp("Tom", "cat!@#123")
+if err != nil {
+ panic(err)
+}
+
+// 设置其他属性
+if err := client.Users.ID(user.ID).Set("email", "tom@xd.com", leancloud.UseUser(user)); err != nil {
+ panic(err)
+}
+```
+
+
+
+如果收到 `202` 错误码,意味着已经存在使用同一 `username` 的账号,此时应提示用户换一个用户名。除此之外,每个用户的 `email` 和 `mobilePhoneNumber` 也需要保持唯一性,否则会收到 `203` 或 `214` 错误。可以考虑在注册时把用户的 `username` 设为与 `email` 相同,这样用户可以直接 [用邮箱重置密码](#重置密码)。
+
+采用「用户名 + 密码」注册时需要注意:密码是以明文方式通过 HTTPS 加密传输给云端,云端会以密文存储密码(云端对密码的长度、复杂度不作限制),并且我们的加密算法是无法通过所谓「彩虹表撞库」获取的,这一点请开发者放心。换言之,用户的密码只可能用户本人知道,开发者不论是通过控制台还是 API 都是无法获取。另外我们需要强调 **在客户端,应用切勿再次对密码加密,这会导致 [重置密码](#重置密码) 等功能失效**。
+
+### 手机号注册
+
+对于移动应用来说,允许用户以手机号注册是个很常见的需求。实现该功能大致分两步,第一步是让用户提供手机号,点击「获取验证码」按钮后,该号码会收到一个六位数的验证码:
+
+
+
+```cs
+await LCSMSClient.RequestSMSCode("+8619201680101");
+```
+
+```java
+LCSMSOption option = new LCSMSOption();
+option.setSignatureName("sign_name"); // 设置短信签名名称
+LCSMS.requestSMSCodeInBackground("+8619201680101", option).subscribe(new Observer() {
+ @Override
+ public void onSubscribe(Disposable disposable) {
+ }
+ @Override
+ public void onNext(LCNull avNull) {
+ Log.d("TAG","Result: succeed to request SMSCode.");
+ }
+ @Override
+ public void onError(Throwable throwable) {
+ Log.d("TAG","Result: failed to request SMSCode. cause:" + throwable.getMessage());
+ }
+ @Override
+ public void onComplete() {
+ }
+});
+```
+
+```objc
+LCShortMessageRequestOptions *options = [[LCShortMessageRequestOptions alloc] init];
+options.templateName = @"template_name"; // 控制台配置好的模板名称
+options.signatureName = @"sign_name"; // 控制台配置好的短信签名名称
+[LCSMS requestShortMessageForPhoneNumber:@"+8619201680101" options:options callback:^(BOOL succeeded, NSError * _Nullable error) {
+ if (succeeded) {
+ /* 请求成功 */
+ } else {
+ /* 请求失败 */
+ }
+}];
+```
+
+```swift
+// templateName 是短信模版名称,signatureName 是短信签名名称。可以在控制台 > 短信 > 设置中查看。
+_ = LCSMSClient.requestShortMessage(mobilePhoneNumber: "+8619201680101", templateName: "template_name", signatureName: "sign_name") { (result) in
+ switch result {
+ case .success:
+ break
+ case .failure(error: let error):
+ print(error)
+ }
+}
+```
+
+```dart
+await LCSMSClient.requestSMSCode('+8619201680101');
+```
+
+```js
+AV.Cloud.requestSmsCode("+8619201680101");
+```
+
+```python
+leancloud.cloud.request_sms_code('+8619201680101')
+```
+
+```php
+SMS::requestSmsCode("+8619201680101");
+```
+
+```go
+// 暂不支持
+```
+
+
+
+用户填入验证码后,用下面的方法完成注册:
+
+
+
+```cs
+await LCUser.SignUpOrLoginByMobilePhone("+8619201680101", "123456");
+```
+
+```java
+LCUser.signUpOrLoginByMobilePhoneInBackground("+8619201680101", "123456").subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCUser user) {
+ // 注册成功
+ System.out.println("注册成功。objectId:" + user.getObjectId());
+ }
+ public void onError(Throwable throwable) {
+ // 验证码不正确
+ }
+ public void onComplete() {}
+});
+```
+
+```objc
+[LCUser signUpOrLoginWithMobilePhoneNumberInBackground:@"+8619201680101" smsCode:@"123456" block:^(LCUser *user, NSError *error) {
+ if (user != nil) {
+ // 注册成功
+ NSLog(@"注册成功。objectId:%@", user.objectId);
+ } else {
+ // 验证码不正确
+ }
+}];
+```
+
+```swift
+_ = LCUser.signUpOrLogIn(mobilePhoneNumber: "+8619201680101", verificationCode: "123456", completion: { (result) in
+ switch result {
+ case .success(object: let user):
+ print(user)
+ case .failure(error: let error):
+ print(error)
+ }
+})
+```
+
+```dart
+ await LCUser.signUpOrLoginByMobilePhone('+8619201680101', '123456');
+```
+
+```js
+AV.User.signUpOrlogInWithMobilePhone("+8619201680101", "123456").then(
+ (user) => {
+ // 注册成功
+ console.log(`注册成功。objectId:${user.id}`);
+ },
+ (error) => {
+ // 验证码不正确
+ }
+);
+```
+
+```python
+user = leancloud.User.signup_or_login_with_mobile_phone('+8619201680101', '123456')
+```
+
+```php
+User::signUpOrLoginByMobilePhone("+8619201680101", "123456");
+```
+
+```go
+user, err := client.Users.SignUpByMobilePhone("+8619201680101", "123456")
+if err != nil {
+ panic(err)
+}
+```
+
+
+
+`username` 将与 `mobilePhoneNumber` 相同,`password` 会由云端随机生成。如果希望让用户指定密码,可以在客户端让用户填写手机号和密码,然后按照上一小节使用用户名和密码注册的流程,将用户填写的手机号作为 `username` 和 `mobilePhoneNumber` 的值同时提交。同时根据业务需求,在**云服务控制台 > 内建账户 > 设置**勾选**未验证手机号码的用户,禁止登录**、**已验证手机号码的用户,允许以短信验证码登录**。
+
+### 手机号格式
+
+云端接受的手机号以 `+` 和国家代码开头,后面紧跟着剩余的部分。手机号中不应含有任何划线、空格等非数字字符。例如,`+15559463664` 是一个合法的美国或加拿大手机号(`1` 是国家代码),`+8619201680101` 是一个合法的中国手机号(`86` 是国家代码)。
+
+请参阅官网的[价格](https://www.leancloud.cn/pricing/)页面以了解支持的国家和地区。
+
+## 登录
+
+下面的代码用用户名和密码登录一个账户:
+
+
+
+```cs
+try {
+ // 登录成功
+ LCUser user = await LCUser.Login("Tom", "cat!@#123");
+} catch (LCException e) {
+ // 登录失败(可能是密码错误)
+ print($"{e.code} : {e.message}");
+}
+```
+
+```java
+LCUser.logIn("Tom", "cat!@#123").subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCUser user) {
+ // 登录成功
+ }
+ public void onError(Throwable throwable) {
+ // 登录失败(可能是密码错误)
+ }
+ public void onComplete() {}
+});
+```
+
+```objc
+[LCUser logInWithUsernameInBackground:@"Tom" password:@"cat!@#123" block:^(LCUser *user, NSError *error) {
+ if (user != nil) {
+ // 登录成功
+ } else {
+ // 登录失败(可能是密码错误)
+ }
+}];
+```
+
+```swift
+_ = LCUser.logIn(username: "Tom", password: "cat!@#123") { result in
+ switch result {
+ case .success(object: let user):
+ print(user)
+ case .failure(error: let error):
+ print(error)
+ }
+}
+```
+
+```dart
+try {
+ // 登录成功
+ LCUser user = await LCUser.login('Tom', 'cat!@#123');
+} on LCException catch (e) {
+ // 登录失败(可能是密码错误)
+ print('${e.code} : ${e.message}');
+}
+```
+
+```js
+AV.User.logIn("Tom", "cat!@#123").then(
+ (user) => {
+ // 登录成功
+ },
+ (error) => {
+ // 登录失败(可能是密码错误)
+ }
+);
+```
+
+```python
+user = leancloud.User()
+user.login(username='Tom', password='cat!@#123')
+```
+
+```php
+User::logIn("Tom", "cat!@#123");
+```
+
+```go
+user, err := client.Users.LogIn("Tom", "cat!@#123")
+if err != nil {
+ panic(err)
+}
+```
+
+
+
+### 邮箱登录
+
+下面的代码用邮箱和密码登录一个账户:
+
+
+
+```cs
+try {
+ // 登录成功
+ LCUser user = await LCUser.LoginByEmail("tom@xd.com", "cat!@#123");
+} catch (LCException e) {
+ // 登录失败(可能是密码错误)
+ print($"{e.code} : {e.message}");
+}
+```
+
+```java
+LCUser.loginByEmail("tom@xd.com", "cat!@#123").subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCUser user) {
+ // 登录成功
+ }
+ public void onError(Throwable throwable) {
+ // 登录失败(可能是密码错误)
+ }
+ public void onComplete() {}
+});
+```
+
+```objc
+[LCUser loginWithEmail:@"tom@xd.com" password:@"cat!@#123" block:^(LCUser *user, NSError *error) {
+ if (user != nil) {
+ // 登录成功
+ } else {
+ // 登录失败(可能是密码错误)
+ }
+}];
+```
+
+```swift
+_ = LCUser.logIn(email: "tom@xd.com", password: "cat!@#123") { result in
+ switch result {
+ case .success(object: let user):
+ print(user)
+ case .failure(error: let error):
+ print(error)
+ }
+}
+```
+
+```dart
+try {
+ // 登录成功
+ LCUser user = await LCUser.loginByEmail('tom@xd.com', 'cat!@#123');
+} on LCException catch (e) {
+ // 登录失败(可能是密码错误)
+ print('${e.code} : ${e.message}');
+}
+```
+
+```js
+AV.User.loginWithEmail("tom@xd.com", "cat!@#123").then(
+ (user) => {
+ // 登录成功
+ },
+ (error) => {
+ // 登录失败(可能是密码错误)
+ }
+);
+```
+
+```python
+user = leancloud.User()
+user.login(email='tom@xd.com', password='cat!@#123')
+```
+
+```php
+User::logInWithEmail("tom@xd.com", "cat!@#123");
+```
+
+```go
+user, err := client.LoginByEmail("tom@xd.com", "cat!@#123")
+if err != nil {
+ panic(err)
+}
+
+fmt.Println(user)
+```
+
+
+
+### 手机号登录
+
+如果应用允许用户以手机号注册,那么也可以让用户以手机号配合密码或短信验证码登录。下面的代码用手机号和密码登录一个账户:
+
+
+
+```cs
+try {
+ // 登录成功
+ LCUser user = await LCUser.LoginByMobilePhoneNumber("+8619201680101", "cat!@#123");
+} catch (LCException e) {
+ // 登录失败(可能是密码错误)
+ print($"{e.code} : {e.message}");
+}
+```
+
+```java
+LCUser.loginByMobilePhoneNumber("+8619201680101", "cat!@#123").subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCUser user) {
+ // 登录成功
+ }
+ public void onError(Throwable throwable) {
+ // 登录失败(可能是密码错误)
+ }
+ public void onComplete() {}
+});
+```
+
+```objc
+[LCUser logInWithMobilePhoneNumberInBackground:@"+8619201680101" password:@"cat!@#123" block:^(LCUser *user, NSError *error) {
+ if (user != nil) {
+ // 登录成功
+ } else {
+ // 登录失败(可能是密码错误)
+ }
+}];
+```
+
+```swift
+_ = LCUser.logIn(mobilePhoneNumber: "+8619201680101", password: "cat!@#123") { result in
+ switch result {
+ case .success(object: let user):
+ print(user)
+ case .failure(error: let error):
+ print(error)
+ }
+}
+```
+
+```dart
+try {
+ // 登录成功
+ LCUser user = await LCUser.loginByMobilePhoneNumber('+8619201680101', 'cat!@#123');
+} on LCException catch (e) {
+ // 登录失败(可能是密码错误)
+ print('${e.code} : ${e.message}');
+}
+```
+
+```js
+AV.User.logInWithMobilePhone("+8619201680101", "cat!@#123").then(
+ (user) => {
+ // 登录成功
+ },
+ (error) => {
+ // 登录失败(可能是密码错误)
+ }
+);
+```
+
+```python
+user = leancloud.User.login_with_mobile_phone('+8619201680101', 'cat!@#123')
+```
+
+```php
+User::logInWithMobilePhoneNumber("+8619201680101", "cat!@#123");
+```
+
+```go
+user, err := client.LogInByMobilePhoneNumber("+8619201680101", "cat!@#123")
+if err != nil {
+ panic(err)
+}
+
+fmt.Println(user)
+```
+
+
+
+默认情况下,云服务允许所有关联了手机号的用户直接以手机号登录,无论手机号是否 [通过验证](#验证手机号)。为了让应用更加安全,你可以选择只允许验证过手机号的用户通过手机号登录。可以在 **控制台 > 内建账户 > 设置** 里面开启该功能。
+
+除此之外,还可以让用户通过短信验证码登录,适用于用户忘记密码且不愿重置密码的情况。和 [通过手机号注册](#手机号注册) 的步骤类似,首先让用户填写与账户关联的手机号码,然后在用户点击「获取验证码」后调用下面的方法:
+
+
+
+```cs
+await LCUser.RequestLoginSMSCode("+8619201680101");
+```
+
+```java
+LCUser.requestLoginSmsCodeInBackground("+8619201680101").subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCNull null) {
+ // 成功调用
+ }
+ public void onError(Throwable throwable) {
+ // 调用出错
+ }
+ public void onComplete() {}
+});
+```
+
+```objc
+[LCUser requestLoginSmsCode:@"+8619201680101"];
+```
+
+```swift
+_ = LCUser.requestLoginVerificationCode(mobilePhoneNumber: "+8619201680101") { result in
+ switch result {
+ case .success:
+ break
+ case .failure(error: let error):
+ print(error)
+ }
+}
+```
+
+```dart
+await LCUser.requestLoginSMSCode('+8619201680101');
+```
+
+```js
+AV.User.requestLoginSmsCode("+8619201680101");
+```
+
+```python
+leancloud.User.request_login_sms_code('+8619201680101')
+```
+
+```php
+SMS::requestSmsCode("+8619201680101");
+```
+
+```go
+if err := client.Users.RequestLoginSMSCode("+8619201680101"); err != nil {
+ panic(err)
+}
+```
+
+
+
+用户填写收到的验证码后,用下面的方法完成登录:
+
+
+
+```cs
+try {
+ // 登录成功
+ await LCUser.SignUpOrLoginByMobilePhone("+8619201680101", "123456");
+} catch (LCException e) {
+ // 验证码不正确
+ print($"{e.code} : {e.message}");
+}
+```
+
+```java
+LCUser.signUpOrLoginByMobilePhoneInBackground("+8619201680101", "123456").subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCUser user) {
+ // 登录成功
+ }
+ public void onError(Throwable throwable) {
+ // 验证码不正确
+ }
+ public void onComplete() {}
+});
+```
+
+```objc
+[LCUser logInWithMobilePhoneNumberInBackground:@"+8619201680101" smsCode:@"123456" block:^(LCUser *user, NSError *error) {
+ if (user != nil) {
+ // 登录成功
+ } else {
+ // 验证码不正确
+ }
+}];
+```
+
+```swift
+_ = LCUser.logIn(mobilePhoneNumber: "+8619201680101", verificationCode: "123456") { result in
+ switch result {
+ case .success(object: let user):
+ print(user)
+ case .failure(error: let error):
+ print(error)
+ }
+}
+```
+
+```dart
+try {
+ // 登录成功
+ await LCUser.signUpOrLoginByMobilePhone('+8619201680101', '123456');
+} on LCException catch (e) {
+ // 验证码不正确
+ print('${e.code} : ${e.message}');
+}
+```
+
+```js
+AV.User.logInWithMobilePhoneSmsCode("+8619201680101", "123456").then(
+ (user) => {
+ // 登录成功
+ },
+ (error) => {
+ // 验证码不正确
+ }
+);
+```
+
+```python
+user = leancloud.User.signup_or_login_with_mobile_phone('+8619201680101', '123456')
+```
+
+```php
+User::signUpOrLoginByMobilePhone("+8619201680101", "123456");
+```
+
+```go
+user, err := client.Users.LogInByMobilePhoneNumber("+8619201680101", "123456")
+if err != nil {
+ panic(err)
+}
+```
+
+
+
+### 测试手机号和固定验证码
+
+在开发过程中,可能会因测试目的而需要频繁地用手机号注册登录,然而运营商的发送频率限制往往会导致测试过程耗费较多的时间。
+
+为了解决这个问题,可以在 **控制台 > 短信 > 设置** 里面设置一个测试手机号,而云端会为该号码生成一个固定验证码。以后进行登录操作时,只要使用的是这个号码,云端就会直接放行,无需经过运营商网络。
+
+测试手机号还可用于将 iOS 应用提交到 App Store 进行审核的场景,因为审核人员可能因没有有效的手机号码而无法登录应用来进行评估审核。如果不提供一个测试手机号,应用有可能被拒绝。
+
+可参阅 [短信 SMS 服务使用指南](/sdk/sms/guide/) 来了解更多有关短信发送和接收的限制。
+
+### 单设备登录
+
+某些场景下需要确保用户的账户在同一时间只在一台设备上登录,也就是说当用户在一台设备上登录后,其他设备上的会话全部失效。可以按照以下方案来实现:
+
+1. 新建一个专门用于记录用户登录信息和当前设备信息的 class。
+2. 每当用户在新设备上登录时,将该 class 中该用户对应的设备更新为该设备。
+3. 在另一台设备上打开客户端时,检查该设备是否与云端保存的一致。若不一致,则将用户 [登出](#当前用户)。
+
+### 账户锁定
+
+输入错误的密码或验证码会导致用户登录失败。如果在 15 分钟内,同一个用户登录失败的次数大于 6 次,该用户账户即被云端暂时锁定,此时云端会返回错误码 `{ "code": 1, "error": "You have exceeded the maximum number of login attempts, please try again later, or consider resetting your password." }`,开发者可在客户端进行必要提示。
+
+锁定将在最后一次错误登录的 15 分钟之后由云端自动解除,开发者无法通过 SDK 或 REST API 进行干预。在锁定期间,即使用户输入了正确的验证信息也不允许登录。这个限制在 SDK 和云引擎中都有效。
+
+## 验证邮箱
+
+可以通过要求用户在登录或使用特定功能之前验证邮箱的方式防止恶意注册。默认情况下,当用户注册或变更邮箱后,`emailVerified` 会被设为 `false`。在应用的 **云服务控制台 > 内建账户 > 设置** 中,可以开启 **启用邮箱验证功能** 选项,这样当用户注册或变更邮箱时,会收到一封含有验证链接的邮件。在同一设置页面还可找到阻止未验证邮箱的用户登录的选项。
+
+如果用户忘记点击链接并且在未来某一时刻需要进行验证,可以用下面的代码发送一封新的邮件:
+
+
+
+```cs
+await LCUser.RequestEmailVerify("tom@xd.com");
+```
+
+```java
+LCUser.requestEmailVerifyInBackground("tom@xd.com").subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCNull null) {
+ // 成功调用
+ }
+ public void onError(Throwable throwable) {
+ // 调用出错
+ }
+ public void onComplete() {}
+});
+```
+
+```objc
+[LCUser requestEmailVerify:@"tom@xd.com"];
+```
+
+```swift
+_ = LCUser.requestVerificationMail(email: "tom@xd.com") { result in
+ switch result {
+ case .success:
+ break
+ case .failure(error: let error):
+ print(error)
+ }
+}
+```
+
+```dart
+await LCUser.requestEmailVerify('tom@xd.com');
+```
+
+```js
+AV.User.requestEmailVerify("tom@xd.com");
+```
+
+```python
+leancloud.User.request_email_verify('tom@xd.com')
+```
+
+```php
+User::requestEmailVerify("tom@xd.com");
+```
+
+```go
+if err := client.Users.RequestEmailVerify("tom@xd.com"); err != nil {
+ panic(err)
+}
+```
+
+
+
+用户点击邮件内的链接后,`emailVerified` 会变为 `true`。如果用户的 `email` 属性为空,则该属性永远不会为 `true`。
+
+## 验证手机号
+
+和 [验证邮箱](#验证邮箱) 类似,应用还可以要求用户在登录或使用特定功能之前验证手机号。默认情况下,当用户注册或变更手机号后,`mobilePhoneVerified` 会被设为 `false`。在应用的 **控制台 > 内建账户 > 设置** 中,可以开启阻止未验证手机号的用户登录的选项。
+
+可以用下面的代码发送一条新的验证码(如果相应用户的 `mobilePhoneVerified` 已经为 `true`,那么验证短信不会发送):
+
+
+
+```cs
+await LCUser.RequestMobilePhoneVerify("+8619201680101");
+```
+
+```java
+LCUser.requestMobilePhoneVerifyInBackground("+8619201680101").subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCNull null) {
+ // 成功调用
+ }
+ public void onError(Throwable throwable) {
+ // 调用出错
+ }
+ public void onComplete() {}
+});
+```
+
+```objc
+[LCUser requestMobilePhoneVerify:@"+8619201680101" withBlock:^(BOOL succeeded, NSError * _Nullable error) {
+ if(succeeded){
+ // 请求成功
+ }else{
+ // 请求失败
+ }
+}];
+```
+
+```swift
+_ = LCUser.requestVerificationCode(mobilePhoneNumber: "+8619201680101") { result in
+ switch result {
+ case .success:
+ break
+ case .failure(error: let error):
+ print(error)
+ }
+}
+```
+
+```dart
+await LCUser.requestMobilePhoneVerify('+8619201680101');
+```
+
+```js
+AV.User.requestMobilePhoneVerify("+8619201680101");
+```
+
+```python
+leancloud.User.request_mobile_phone_verify('+8619201680101')
+```
+
+```php
+User::requestMobilePhoneVerify("+8619201680101");
+```
+
+```go
+if err := client.Users.RequestMobilePhoneVerify("+8619201680101"); err != nil {
+ panic(err)
+}
+```
+
+
+
+用户填写验证码后,调用下面的方法来完成验证。`mobilePhoneVerified` 将变为 `true`:
-```cs
-try{
- // tdsUSer will hold a unique identifier of the user, if it exists
- var tdsUser = await TDSUser.LoginAnonymously();
-}catch(Exception e){
- // Failed to log in
- Debug.Log($"{e.code} : {e.message}");
+```cs
+await LCUser.VerifyMobilePhone("+8619201680101", "123456");
+```
+
+```java
+LCUser.verifyMobilePhoneInBackground("123456").subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCNull null) {
+ // mobilePhoneVerified 将变为 true
+ }
+ public void onError(Throwable throwable) {
+ // 验证码不正确
+ }
+ public void onComplete() {}
+});
+```
+
+```objc
+[LCUser verifyMobilePhone:@"123456" withBlock:^(BOOL succeeded, NSError * _Nullable error) {
+ if(succeeded){
+ // mobilePhoneVerified 将变为 true
+ }else{
+ // 验证码不正确
+ }
+}];
+```
+
+```swift
+_ = LCUser.verifyMobilePhoneNumber(mobilePhoneNumber: "+8619201680101", verificationCode: "123456") { result in
+ switch result {
+ case .success:
+ // mobilePhoneVerified 将变为 true
+ break
+ case .failure(error: let error):
+ // 验证码不正确
+ print(error)
+ }
}
```
-```java
-TDSUser.logInAnonymously().subscribe(new Observer() {
- @Override
- public void onSubscribe(Disposable disposable) {
+```dart
+await LCUser.verifyMobilePhone('+8619201680101','123456');
+```
+```js
+AV.User.verifyMobilePhone("123456").then(
+ () => {
+ // mobilePhoneVerified 将变为 true
+ },
+ (error) => {
+ // 验证码不正确
}
+);
+```
- @Override
- public void onNext(TDSUser resultUser) {
- // Successfully logged in and obtained an account instance
- String userId = resultUser.getObjectId();
- }
+```python
+leancloud.User.verify_mobile_phone_number('123456')
+```
- @Override
- public void onError(Throwable throwable) {
+```php
+User::verifyMobilePhone("123456");
+```
- }
+```go
+// 暂不支持
+```
- @Override
- public void onComplete() {
- }
+
+
+### 绑定、修改手机号之前先验证
+
+除了在用户绑定、修改手机号**之后**进行验证,云服务也支持在用户绑定或修改手机号**之前**先通过短信验证。也就是说,绑定手机号或修改手机号时先请求发送验证码(用户需处于登录状态),再凭短信验证码完成绑定或修改操作。
+
+
+
+```cs
+await LCUser.RequestSMSCodeForUpdatingPhoneNumber("+8619201680101");
+
+await LCUser.VerifyCodeForUpdatingPhoneNumber("+8619201680101", "123456");
+// 更新本地数据
+LCUser currentUser = await LCUser.GetCurrent();
+user.Mobile = "+8619201680101";
+```
+
+```java
+LCUser.requestSMSCodeForUpdatingPhoneNumberInBackground("+8619201680101",null).subscribe(new Observer() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ }
+ @Override
+ public void onNext(@NonNull LCNull lcNull) {
+ // 成功调用
+ }
+ @Override
+ public void onError(@NonNull Throwable e) {
+ // 调用出错
+ }
+ @Override
+ public void onComplete() {
+ }
+});
+
+LCUser.verifySMSCodeForUpdatingPhoneNumberInBackground("123456", "+8619201680101").subscribe(new Observer() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ }
+ @Override
+ public void onNext(@NonNull LCNull lcNull) {
+ // 更新本地数据
+ LCUser currentUser = LCUser.getCurrentUser();
+ currentUser.setMobilePhoneNumber("+8619201680101");
+ }
+ @Override
+ public void onError(@NonNull Throwable e) {
+ // 验证码不正确
+ }
+ @Override
+ public void onComplete() {
+ }
});
```
-```objectivec
-[TDSUser loginAnonymously:^(TDSUser * _Nullable user, NSError * _Nullable error) {
- if (user) {
- NSString *userId = user.objectId;
+```objc
+[LCUser requestVerificationCodeForUpdatingPhoneNumber:@"+8619201680101" withBlock:^(BOOL succeeded, NSError * _Nullable error) {
+ if (succeeded) {
+ // 请求成功
+ } else {
+ // 请求失败
+ }
+}];
+
+[LCUser verifyCodeToUpdatePhoneNumber:@"+8619201680101" code:@"123456" withBlock:^(BOOL succeeded, NSError * _Nullable error) {
+ if (succeeded) {
+ // mobilePhoneNumber 变为 +8619201680101
+ // mobilePhoneVerified 变为 true
} else {
- NSLog(@"%@", error);
+ // 验证码不正确
}
}];
```
-
+```swift
+_ = LCUser.requestVerificationCode(forUpdatingMobilePhoneNumber: "+8619201680101") { result in
+ switch result {
+ case .success:
+ break
+ case .failure(error: let error):
+ print(error)
+ }
+}
+
+_ = LCUser.verifyVerificationCode("123456", toUpdateMobilePhoneNumber:"+8619201680101") { result in
+ switch result {
+ case .success:
+ // mobilePhoneNumber 变为 +8619201680101
+ // mobilePhoneVerified 变为 true
+ break
+ case .failure(error: let error):
+ // 验证码不正确
+ print(error)
+ }
+}
+```
+
+```dart
+await LCUser.requestSMSCodeForUpdatingPhoneNumber('+8619201680101');
+
+await LCUser.verifyCodeForUpdatingPhoneNumber('+8619201680101', '123456');
+// 更新本地数据
+LCUser currentUser = await LCUser.getCurrent();
+user.mobile = '+8619201680101';
+```
+
+```js
+AV.User.requestChangePhoneNumber("+8619201680101");
+
+AV.User.changePhoneNumber("+8619201680101", "123456").then(
+ () => {
+ // 更新本地数据
+ const currentUser = AV.User.current();
+ currentUser.setMobilePhoneNumber("+8619201680101");
+ },
+ (error) => {
+ // 验证码不正确
+ }
+);
+```
+
+```python
+User.request_change_phone_number("+8619201680101")
+
+User.change_phone_number("123456", "+8619201680101")
+# 更新本地数据
+current_user = leancloud.User.get_current()
+current_user.set_mobile_phone_number("+8619201680101")
+```
+
+```php
+User::requestChangePhoneNumber("+8619201680101");
+
+User::changePhoneNumber("123456", "+8619201680101");
+// 更新本地数据
+$currentUser = User::getCurrentUser();
+$user->setMobilePhoneNumber("+8619201680101");
+```
-:::info
+```go
+if err := client.Users.requestChangePhoneNumber("+8619201680101"); err != nil {
+ panic(err)
+}
-The guest account ensures that the player will have access to the same account on different logins. However, if the player deletes the game and then reinstalls the game, it is not guaranteed that the player will still have access to the same account.
+if err := client.Users.ChangePhoneNumber("123456", "+8619201680101"); err != nil {
+ panic(err)
+}
+```
-:::
+
-## Current User
+## 当前用户
-Once the user has logged in, the SDK will automatically save the session to the client so that the user won’t need to log in again the next time they open the client. The code below checks if there is a logged-in user:
+用户登录后,SDK 会自动将会话信息存储到客户端,这样用户在下次打开客户端时无需再次登录。下面的代码检查是否有已经登录的用户:
```cs
-TDSUser currentUser = await TDSUser.GetCurrent();
+LCUser currentUser = await LCUser.GetCurrent();
if (currentUser != null) {
- // Go to homepage
+ // 跳到首页
} else {
- // Show the sign-up or the log-in page
+ // 显示注册或登录页面
}
```
```java
-TDSUser currentUser = TDSUser.getCurrentUser();
+LCUser currentUser = LCUser.getCurrentUser();
if (currentUser != null) {
- // Go to homepage
+ // 跳到首页
} else {
- // Show the sign-up or the log-in page
+ // 显示注册或登录页面
}
```
```objc
-TDSUser *currentUser = [TDSUser currentUser];
+LCUser *currentUser = [LCUser currentUser];
if (currentUser != nil) {
- // Go to homepage
+ // 跳到首页
+} else {
+ // 显示注册或登录页面
+}
+```
+
+```swift
+if let user = LCApplication.default.currentUser {
+ // 跳到首页
} else {
- // Show the sign-up or the log-in page
+ // 显示注册或登录页面
}
```
+```dart
+LCUser currentUser = await LCUser.getCurrent();
+if (currentUser != null) {
+ // 跳到首页
+} else {
+ // 显示注册或登录页面
+}
+```
+
+```js
+const currentUser = AV.User.current();
+if (currentUser) {
+ // 跳到首页
+} else {
+ // 显示注册或登录页面
+}
+```
+
+```python
+current_user = leancloud.User.get_current()
+if current_user is not None:
+ # 跳到首页
+ pass
+else:
+ # 显示注册或登录页面
+ pass
+```
+
+```php
+$currentUser = User::getCurrentUser();
+if ($currentUser != null) {
+ // 跳到首页
+} else {
+ // 显示注册或登录页面
+}
+```
+
+```go
+// 暂不支持
+```
+
-The session will remain valid until the user logs out:
+会话信息会长期有效,直到用户主动登出:
```cs
-await TDSUser.Logout();
+await LCUser.Logout();
-// currentUser becomes null
-TDSUser currentUser = await TDSUser.GetCurrent();
+// currentUser 变为 null
+LCUser currentUser = await LCUser.GetCurrent();
```
```java
-TDSUser.logOut();
+LCUser.logOut();
-// currentUser becomes null
-TDSUser currentUser = TDSUser.getCurrentUser();
+// currentUser 变为 null
+LCUser currentUser = LCUser.getCurrentUser();
```
```objc
-[TDSUser logOut];
+[LCUser logOut];
+
+// currentUser 变为 nil
+LCUser *currentUser = [LCUser currentUser];
+```
+
+```swift
+LCUser.logOut()
+
+// currentUser 变为 nil
+let currentUser = LCApplication.default.currentUser
+```
+
+```dart
+await LCUser.logout();
+
+// currentUser 变为 null
+LCUser currentUser = await LCUser.getCurrent();
+```
+
+```js
+AV.User.logOut();
+
+// currentUser 变为 null
+const currentUser = AV.User.current();
+```
+
+```python
+user.logout()
+
+current_user = leancloud.User.get_current() # None
+```
+
+```php
+User::logOut();
-// currentUser becomes nil
-TDSUser *currentUser = [TDSUser currentUser];
+// currentUser 变为 null
+$currentUser = User::getCurrentUser();
+```
+
+```go
+// 暂不支持
```
-## Setting the Current User
+## 设置当前用户
-A **session token** will be returned to the client after a user is logged in. It will be cached by our SDK and will be used for authenticating requests made by the same `TDSUser` in the future. The session token will be included in the header of each HTTP request made by the client, which helps the cloud identify the `TDSUser` sending the request.
+用户登录后,云端会返回一个 **session token** 给客户端,它会由 SDK 缓存起来并用于日后同一用户的鉴权请求。session token 会被包含在每个客户端发起的 HTTP 请求的 header 里面,这样云端就知道是哪个用户发起的请求了。
-Below are the situations when you may need to log a user in with their session token:
+以下是一些应用可能需要用到 session token 的场景:
-- A session token is already cached on the client which can be used to automatically log the user in (you can get the session token of the current user by accessing the `sessionToken` property; you can also get the `sessionToken` of any user on the server side with your Master Key (also called Server Secret)).
-- A WebView within the app needs to know the current user.
-- The user is logged in on the server side using your own authentication routines and the server is able to provide the session token to the client.
+- 应用根据以前缓存的 session token 登录。
+- 应用内的某个 WebView 需要知道当前登录的用户。
+- 在服务端登录后,返回 session token 给客户端,客户端根据返回的 session token 登录。
-The code below logs a user in with a session token (the session token will be validated before proceeding):
+下面的代码使用 session token 登录一个用户(云端会验证 session token 是否有效):
```cs
-await TDSUser.BecomeWithSessionToken("anmlwi96s381m6ca7o7266pzf");
+await LCUser.BecomeWithSessionToken("anmlwi96s381m6ca7o7266pzf");
```
```java
-TDSUser.becomeWithSessionTokenInBackground("anmlwi96s381m6ca7o7266pzf").subscribe(new Observer() {
+LCUser.becomeWithSessionTokenInBackground("anmlwi96s381m6ca7o7266pzf").subscribe(new Observer() {
public void onSubscribe(Disposable disposable) {}
- public void onNext(TDSUser user) {
- // Update currentUser
- TDSUser.changeCurrentUser(user, true);
+ public void onNext(LCUser user) {
+ // 修改 currentUser
+ LCUser.changeCurrentUser(user, true);
}
public void onError(Throwable throwable) {
- // session token is invalid
+ // session token 无效
}
public void onComplete() {}
});
```
```objc
-[TDSUser becomeWithSessionTokenInBackground:@"anmlwi96s381m6ca7o7266pzf" block:^(TDSUser * _Nullable user, NSError * _Nullable error) {
+[LCUser becomeWithSessionTokenInBackground:@"anmlwi96s381m6ca7o7266pzf" block:^(LCUser * _Nullable user, NSError * _Nullable error) {
if (user != nil) {
- // Successfully logged in
+ // 登录成功
} else {
- // session token is invalid
+ // session token 无效
}
}];
```
+```swift
+_ = LCUser.logIn(sessionToken: "anmlwi96s381m6ca7o7266pzf") { (result) in
+ switch result {
+ case .success(object: let user):
+ // 登录成功
+ print(user)
+ case .failure(error: let error):
+ // session token 无效
+ print(error)
+ }
+}
+```
+
+```dart
+await LCUser.becomeWithSessionToken('anmlwi96s381m6ca7o7266pzf');
+```
+
+```js
+AV.User.become("anmlwi96s381m6ca7o7266pzf").then(
+ (user) => {
+ // 登录成功
+ },
+ (error) => {
+ // session token 无效
+ }
+);
+```
+
+```python
+user = leancloud.User.become('anmlwi96s381m6ca7o7266pzf')
+```
+
+```php
+User::become("anmlwi96s381m6ca7o7266pzf");
+```
+
+```go
+user, err := client.Users.Become("anmlwi96s381m6ca7o7266pzf")
+if err != nil {
+ panic(err)
+}
+```
+
-For security reasons, please avoid passing URLs containing session tokens in non-private environments. This increases the risk of your session tokens being captured by attackers.
+请避免在外部浏览器使用 URL 来传递 session token,以防范信息泄露风险。
-If **Log out the user when password is updated** is enabled on **Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings**, the session token of a user will be reset in the cloud after this user changes the password and the client needs to prompt the user to log in again. Otherwise, `403 (Forbidden)` will be returned as an error.
+如果在 **控制台 > 内建账户 > 设置** 中勾选了 **密码修改后,强制客户端重新登录**,那么当一个用户修改密码后,该用户的 session token 会被重置。此时需要让用户重新登录,否则会遇到 `403 (Forbidden)` 错误。
-The code below checks if a session token is valid:
+下面的代码检查 session token 是否有效:
```cs
-TDSUser currentUser = await TDSUser.GetCurrent();
+LCUser currentUser = await LCUser.GetCurrent();
bool isAuthenticated = await currentUser.IsAuthenticated();
if (isAuthenticated) {
- // session token is valid
+ // session token 有效
} else {
- // session token is invalid
+ // session token 无效
}
```
```java
-boolean authenticated = TDSUser.getCurrentUser().isAuthenticated();
+boolean authenticated = LCUser.getCurrentUser().isAuthenticated();
if (authenticated) {
- // session token is valid
+ // session token 有效
} else {
- // session token is invalid
+ // session token 无效
}
```
```objc
-TDSUser *currentUser = [TDSUser currentUser];
+LCUser *currentUser = [LCUser currentUser];
NSString *token = currentUser.sessionToken;
[currentUser isAuthenticatedWithSessionToken:token callback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
- // session token is valid
+ // session token 有效
} else {
- // session token is invalid
+ // session token 无效
}
}];
```
+```swift
+if let sessionToken = LCApplication.default.currentUser?.sessionToken?.value {
+ _ = LCUser.logIn(sessionToken: sessionToken) { (result) in
+ if result.isSuccess {
+ // session token 有效
+ } else {
+ // session token 无效
+ }
+ }
+}
+```
+
+```dart
+LCUser currentUser = await LCUser.getCurrent();
+bool isAuthenticated = await currentUser.isAuthenticated();
+if (isAuthenticated) {
+ // session token 有效
+} else {
+ // session token 无效
+}
+```
+
+```js
+const currentUser = AV.User.current();
+currentUser.isAuthenticated().then((authenticated) => {
+ if (authenticated) {
+ // session token 有效
+ } else {
+ // session token 无效
+ }
+});
+```
+
+```python
+authenticated = leancloud.User.get_current().is_authenticated()
+if authenticated:
+ # session token 有效
+ pass
+else:
+ # session token 无效
+ pass
+```
+
+```php
+$authenticated = User::isAuthenticated();
+if ($authenticated) {
+ // session token 有效
+} else {
+ // session token 无效
+}
+```
+
+```go
+// 暂不支持
+```
+
-## Setting Other User Properties
+## 重置密码
+
+我们都知道,应用一旦加入账户密码系统,那么肯定会有用户忘记密码的情况发生。对于这种情况,我们为用户提供了多种重置密码的方法。
+
+邮箱重置密码的流程如下:
+
+1. 用户输入注册的电子邮箱,请求重置密码;
+2. 云端向该邮箱发送一封包含重置密码的特殊链接的电子邮件;
+3. 用户点击重置密码链接后,一个特殊的页面会打开,让他们输入新密码;
+4. 用户的密码已被重置为新输入的密码。
-The account system allows you to store `nickname` and `avatar` data associated with users. For example, you can store users’ nicknames by using `nickname` field.
+首先让用户填写注册账户时使用的邮箱,然后调用下面的方法:
```cs
-var currentUser = await TDSUser.GetCurrent(); // Get the instance of the current user
-currentUser["nickname"] = "Tarara";
-await currentUser.Save();
+await LCUser.RequestPasswordReset("tom@xd.com");
```
```java
-TDSUser currentUser = TDSUser.currentUser(); // Get the instance of the current user
-currentUser.put("nickname", "Tarara");
-currentUser.saveInBackground().subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable d) {
+LCUser.requestPasswordResetInBackground("tom@xd.com").subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCNull null) {
+ // 成功调用
+ }
+ public void onError(Throwable throwable) {
+ // 调用出错
+ }
+ public void onComplete() {}
+});
+```
+```objc
+[LCUser requestPasswordResetForEmailInBackground:@"tom@xd.com"];
+```
+
+```swift
+_ = LCUser.requestPasswordReset(email: "tom@xd.com") { (result) in
+ switch result {
+ case .success:
+ break
+ case .failure(error: let error):
+ print(error)
}
+}
+```
- @Override
- public void onNext(@NotNull LCObject lcObject) {
- // Saved; the properties of currentUser are updated
- TDSUser tdsUser = (TDSUser) lcObject;
+```dart
+await LCUser.requestPasswordReset('tom@xd.com');
+```
+
+```js
+AV.User.requestPasswordReset("tom@xd.com");
+```
+
+```python
+leancloud.User.request_password_reset('tom@xd.com')
+```
+
+```php
+User::requestPasswordReset("tom@xd.com");
+```
+
+```go
+if err := client.Users.RequestPasswordReset("tom@xd.com"); err != nil {
+ panic(err)
+}
+```
+
+
+
+上面的代码会查询是否有用户的 `email` 属性与前面提供的邮箱匹配。如果有的话,则向该邮箱发送一封密码重置邮件。之前提到过,应用可以让 `username` 与 `email` 保持一致,也可以单独收集用户的邮箱并将其存为 `email`。
+
+密码重置邮件的内容可在应用的 **云服务控制台 > 内建账户 > 邮件模版** 中自定义。更多关于自定义邮件模板和验证链接的内容,请参考《自定义邮件验证和重设密码页面》。
+
+除此之外,还可以用手机号重置密码:
+
+1. 用户输入注册的手机号,请求重置密码;
+2. 云端向该号码发送一条包含验证码的短信;
+3. 用户输入验证码和新密码。
+
+下面的代码向用户发送含有验证码的短信:
+
+
+
+```cs
+await LCUser.RequestPasswordRestBySmsCode("+8619201680101");
+```
+
+```java
+LCUser.requestPasswordResetBySmsCodeInBackground("+8619201680101").subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCNull null) {
+ // 成功调用
+ }
+ public void onError(Throwable throwable) {
+ // 调用出错
}
+ public void onComplete() {}
+});
+```
- @Override
- public void onError(@NotNull Throwable e) {
+```objc
+[LCUser requestPasswordResetWithPhoneNumber:@"+8619201680101"];
+```
+```swift
+_ = LCUser.requestPasswordReset(mobilePhoneNumber: "+8619201680101") { (result) in
+ switch result {
+ case .success:
+ break
+ case .failure(error: let error):
+ print(error)
}
+}
+```
- @Override
- public void onComplete() {
+```dart
+await LCUser.requestPasswordRestBySmsCode('+8619201680101');
+```
+
+```js
+AV.User.requestPasswordResetBySmsCode("+8619201680101");
+```
+
+```python
+leancloud.User.request_password_reset_by_sms_code('+8619201680101')
+```
+
+```php
+User::requestPasswordResetBySmsCode("+8619201680101");
+```
+
+```go
+if err := client.Users.RequestPasswordResetBySmsCode("+8619201680101"); err != nil {
+ panic(err)
+}
+```
+
+
+
+上面的代码会查询是否有用户的 `mobilePhoneNumber` 属性与前面提供的手机号匹配。如果有的话,则向该号码发送验证码短信。
+
+可以在 **云服务控制台 > 内建账户 > 设置** 中设置只有在 `mobilePhoneVerified` 为 `true` 的情况下才能用手机号重置密码。
+
+用户输入验证码和新密码后,用下面的代码完成密码重置:
+
+
+```cs
+await LCUser.ResetPasswordBySmsCode("+8619201680101", "123456", "cat!@#123");
+```
+
+```java
+LCUser.resetPasswordBySmsCodeInBackground("123456", "cat!@#123").subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCNull null) {
+ // 密码重置成功
+ }
+ public void onError(Throwable throwable) {
+ // 验证码不正确
}
+ public void onComplete() {}
});
```
-```objectivec
-TDSUser *currentUser = [TDSUser currentUser];
-currentUser[@"nickname"] = @"Tarara";
-[currentUser saveInBackgroundWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
+```objc
+[LCUser resetPasswordWithSmsCode:@"123456" newPassword:@"cat!@#123" block:^(BOOL succeeded, NSError *error) {
if (succeeded) {
- // Saved
+ // 密码重置成功
} else {
- NSLog(@"%@", error);
+ // 验证码不正确
}
}];
```
-
+```swift
+_ = LCUser.resetPassword(mobilePhoneNumber: "+8619201680101", verificationCode: "123456", newPassword: "cat!@#123") { result in
+ switch result {
+ case .success:
+ // 密码重置成功
+ break
+ case .failure(error: let error):
+ // 验证码不正确
+ print(error)
+ }
+}
+```
+
+```dart
+await LCUser.resetPasswordBySmsCode('+8619201680101', '123456', 'cat!@#123');
+```
+
+```js
+AV.User.resetPasswordBySmsCode("123456", "cat!@#123").then(
+ () => {
+ // 密码重置成功
+ },
+ (error) => {
+ // 验证码不正确
+ }
+);
+```
+
+```python
+leancloud.User.reset_password_by_sms_code('123456', 'cat!@#123')
+```
+
+```php
+User::resetPasswordBySmsCode("123456", "cat!@#123");
+```
+
+```go
+if err := client.Users.ResetPasswordBySmsCode("+8619201680101", "123456", "cat!@#123"); err != nil {
+ panic(err)
+}
+```
+
+
+
+## 用户的查询
+
+使用下面的代码来查询用户:
+
+
+
+```cs
+LCQuery userQuery = LCUser.GetQuery();
+```
+
+```java
+LCQuery userQuery = LCUser.getQuery();
+```
-The account system supports only two extra fields besides the built-in ones: `nickname` and `avatar`. Adding other new fields will cause an error.
+```objc
+LCQuery *userQuery = [LCUser query];
+```
-The account system contains users’ authentication data as well as emails and phone numbers, so there will be strict permission settings imposed on it to prevent the leak of the data.
+```swift
+let userQuery = LCQuery(className: "_User")
+```
-Besides the security concerns, having too much data in the account system can also lead to performance issues like the occurrence of slow queries.
+```dart
+LCQuery userQuery = LCUser.getQuery();
+```
-Therefore, we restrict the use of fields. If you want to store other user information, we suggest that you create a dedicated class (like `UserProfile`) to store it.
+```js
+const userQuery = new AV.Query("_User");
+```
-:::tip
-We recommend that you store users’ nicknames with the `nickname` field because [TDS’s Friends module](/sdk/friends/guide/) **uses the data in this field when looking up friends with nicknames or generating invitation links**.
+```python
+user_query = leancloud.Query('_leancloud.User')
+```
-If you log a user in [with the result of TapTap OAuth](/sdk/taptap-login/guide/start/#quick-log-in-with-taptap), the SDK will automatically set the `nickname` of the user to be the username of their TapTap account.
-:::
+```php
+$userQuery = new Query("_User");
+```
-## Queries on Users
+```go
+userQuery := client.Users.NewUserQuery()
+```
-`TDSUser` is a subclass of `LCObject`. This means that you can create, read, update, and delete user objects in the same way as you do with `LCObject`s. See [Data Storage Overview](/sdk/storage/features/) for more details.
+
-For security reasons, **the account system (the `_User` table) has its `find` permission disabled by default**. Each user can only access their own data in the `_User` table and cannot access that of others. If you need to allow each user to view other users’ data, we recommend that you create a new table to store such data and enable the `find` permission of this table. You may also encapsulate queries on users within Cloud Engine and avoid opening up `find` permissions of `_User` tables.
+为了安全起见,**新创建的应用的 `_User` 表默认关闭了 `find` 权限**,这样每位用户登录后只能查询到自己在 `_User` 表中的数据,无法查询其他用户的数据。如果需要让其查询其他用户的数据,建议单独创建一张表来保存这类数据,并开放这张表的 `find` 查询权限。除此之外,还可以在 [云引擎](/sdk/engine/overview) 里封装用户查询相关的方法。
-See [Security of User Objects](#security-of-user-objects) for other restrictions applied to the `_User` table and [Data Security](/sdk/storage/guide/security/) for more information regarding class-level permission settings.
+可以参见 [用户对象的安全](#用户对象的安全) 来了解 `_User` 表的一些限制,还可以阅读[《数据和安全》](/sdk/storage/guide/security)来了解更多 class 级权限设置的方法。
-## Associations
+## 关联用户对象
-Associations involving `TDSUser`s work in the same way as that of `LCObject`s. The code below saves a new book for an author and retrieves all the books written by that author:
+关联用户的方法和对象是一样的。下面的代码为一名作者保存了一本书,然后获取所有该作者写的书:
```cs
LCObject book = new LCObject("Book");
-TDSUser author = await LCUser.GetCurrent();
-book["title"] = "My Fifth Book";
+LCUser author = await LCUser.GetCurrent();
+book["title"] = "我的第五本书";
book["author"] = author;
await book.Save();
LCQuery query = new LCQuery("Book");
query.WhereEqualTo("author", author);
-// books is an array of Book objects by the same author
+// books 是包含同一作者所有 Book 对象的数组
ReadOnlyCollection books = await query.Find();
```
```java
LCObject book = new LCObject("Book");
-TDSUser author = TDSUser.getCurrentUser();
-book.put("title", "My Fifth Book");
+LCUser author = LCUser.getCurrentUser();
+book.put("title", "我的第五本书");
book.put("author", author);
book.saveInBackground().subscribe(new Observer() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCObject book) {
- // Find all the books by the same author
+ // 获取所有该作者写的书
LCQuery query = new LCQuery<>("Book");
query.whereEqualTo("author", author);
query.findInBackground().subscribe(new Observer>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(List books) {
- // books is an array of Book objects by the same author
+ // books 是包含同一作者所有 Book 对象的数组
}
public void onError(Throwable throwable) {}
public void onComplete() {}
@@ -349,45 +1885,139 @@ book.saveInBackground().subscribe(new Observer() {
```objc
LCObject *book = [LCObject objectWithClassName:@"Book"];
-TDSUser *author = [TDSUser currentUser];
-[book setObject:@"My Fifth Book" forKey:@"title"];
+LCUser *author = [LCUser currentUser];
+[book setObject:@"我的第五本书" forKey:@"title"];
[book setObject:author forKey:@"author"];
[book saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
- // Find all the books by the same author
+ // 获取所有该作者写的书
LCQuery *query = [LCQuery queryWithClassName:@"Book"];
[query whereKey:@"author" equalTo:author];
[query findObjectsInBackgroundWithBlock:^(NSArray *books, NSError *error) {
- // books is an array of Book objects by the same author
+ // books 是包含同一作者所有 Book 对象的数组
}];
}];
```
+```swift
+do {
+ guard let author = LCApplication.default.currentUser else {
+ return
+ }
+ let book = LCObject(className: "Book")
+ try book.set("title", value: "我的第五本书")
+ try book.set("author", value: author)
+ _ = book.save { result in
+ switch result {
+ case .success:
+ // 获取所有该作者写的书
+ let query = LCQuery(className: "Book")
+ query.whereKey("author", .equalTo(author))
+ _ = query.find { result in
+ switch result {
+ case .success(objects: let books):
+ // books 是包含同一作者所有 Book 对象的数组
+ break
+ case .failure(error: let error):
+ print(error)
+ }
+ }
+ case .failure(error: let error):
+ print(error)
+ }
+ }
+} catch {
+ print(error)
+}
+```
+
+```dart
+LCObject book = LCObject('Book');
+LCUser author = await LCUser.getCurrent();
+book['title'] = '我的第五本书';
+book['author'] = author;
+await book.save();
+
+LCQuery query = LCQuery('Book');
+query.whereEqualTo('author', author);
+// books 是包含同一作者所有 Book 对象的数组
+List books = await query.find();
+```
+
+```js
+const Book = AV.Object.extend("Book");
+const book = new Book();
+const author = AV.User.current();
+book.set("title", "我的第五本书");
+book.set("author", author);
+book.save().then((book) => {
+ // 获取所有该作者写的书
+ const query = new AV.Query("Book");
+ query.equalTo("author", author);
+ query.find().then((books) => {
+ // books 是包含同一作者所有 Book 对象的数组
+ });
+});
+```
+
+```python
+Book = leancloud.Object.extend('Book')
+book = Book()
+author = leancloud.User.get_current()
+book.set('title', '我的第五本书')
+book.set('author', author)
+book.save()
+
+# 获取所有该作者写的书
+query = Book.query
+query.equal_to('author', author)
+book_list = query.find()
+```
+
+```php
+$book = new LeanObject("Book");
+$author = User::getCurrentUser();
+$book->set("title", "我的第五本书");
+$book->set("author", $author);
+$book->save();
+
+// 获取所有该作者写的书
+$query = new Query("Book");
+$query->equalTo("author", $author);
+$books = $query->find();
+```
+
+```go
+// 暂不支持
+```
+
-## Security of User Objects
+## 用户对象的安全
-The `TDSUser` class is secured by default. You are not able to invoke any save- or delete-related methods unless the `TDSUser` was obtained using an authenticated method like logging in. This ensures that each user can only update their own data.
+用户对象自带安全保障,只有通过经过鉴权的方法获取到的用户对象才能进行更新或删除操作,保证每个用户只能修改自己的数据。
-The reason behind this is that most data stored in `TDSUser` can be very personal and sensitive, such as mobile phone numbers, social network account IDs, etc. Even the app’s owner should avoid tampering with these data for the sake of users’ privacy.
+这样设计是因为用户对象中存储的大多数数据都比较敏感,包括手机号、社交网络账号等等。为了用户的隐私安全,即使是应用的开发者也应避免直接接触这些数据。
-The code below illustrates this security policy:
+下面的代码展现了这种安全措施:
```cs
try {
- TDSUser tdsUser = await TDSUser.LoginWithTapTap();
- // Attempt to change username
+ LCUser user = await LCUser.Login("Tom", "cat!@#123");
+ // 试图修改用户名
user["username"] = "Jerry";
- // This will work since the user is authenticated
+ // 密码已被加密,这样做会获取到空字符串
+ string password = user["password"];
+ // 可以执行,因为用户已鉴权
await user.Save();
- // Get the user with a non-authenticated method
- LCQuery userQuery = TDSUser.GetQuery();
- TDSUser unauthenticatedUser = await userQuery.Get(user.ObjectId);
+ // 绕过鉴权直接获取用户
+ LCQuery userQuery = LCUser.GetQuery();
+ LCUser unauthenticatedUser = await userQuery.Get(user.ObjectId);
unauthenticatedUser["username"] = "Toodle";
- // This will cause an error since the user is unauthenticated
+ // 会出错,因为用户未鉴权
unauthenticatedUser.Save();
} catch (LCException e) {
print($"{e.code} : {e.message}");
@@ -395,119 +2025,310 @@ try {
```
```java
-TDSUser.loginWithTapTap(MainActivity.this, new Callback() {
- @Override
- public void onSuccess(TDSUser resultUser) {
- Toast.makeText(MainActivity.this, "Logged in with TapTap.", Toast.LENGTH_SHORT).show();
- // This will work since the user is authenticated
- resultUser.put("username", "Toodle");
- // For demonstration only; please use the asynchronous method in your project to avoid blocking the thread
- resultUser.save();
-
- // Get the user with a non-authenticated method
- LCQuery query = new LCQuery<>("_User");
- query.getInBackground(user.getObjectId()).subscribe(new Observer() {
+LCUser.logIn("Tom", "cat!@#123").subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCUser user) {
+ // 试图修改用户名
+ user.put("username", "Jerry");
+ // 密码已被加密,这样做会获取到空字符串
+ String password = user.getString("password");
+ // 可以执行,因为用户已鉴权
+ user.save();
+
+ // 绕过鉴权直接获取用户
+ LCQuery query = new LCQuery<>("_User");
+ query.getInBackground(user.getObjectId()).subscribe(new Observer() {
public void onSubscribe(Disposable disposable) {}
- public void onNext(TDSUser unauthenticatedUser) {
+ public void onNext(LCUser unauthenticatedUser) {
unauthenticatedUser.put("username", "Toodle");
- // This will cause an error since the user is unauthenticated
+ // 会出错,因为用户未鉴权
unauthenticatedUser.save();
}
public void onError(Throwable throwable) {}
public void onComplete() {}
});
}
-
- @Override
- public void onFail(TapError error) {
- Toast.makeText(MainActivity.this, error.getMessage(), Toast.LENGTH_SHORT).show();
- }
-}, "public_profile");
+ public void onError(Throwable throwable) {}
+ public void onComplete() {}
+});
```
```objc
-[TDSUser loginByTapTapWithPermissions:@[@"public_profile"] callback:^(TDSUser * _Nullable user, NSError * _Nullable error) {
- if (user) {
- // Attempt to change username
+[LCUser logInWithUsernameInBackground:@"Tom" password:@"cat!@#123" block:^(LCUser *user, NSError *error) {
+ if (user != nil) {
+ // 试图修改用户名
[user setObject:@"Jerry" forKey:@"username")];
- // Save changes
+ // 密码已被加密,这样做会获取到空字符串
+ NSString *password = user[@"password"];
+ // 保存更改
[user saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
- // This will work since the user is authenticated
+ // 可以执行,因为用户已鉴权
- // Get the user with a non-authenticated method
- LCQuery *query = [TDSUser query];
+ // 绕过鉴权直接获取用户
+ LCQuery *query = [LCQuery queryWithClassName:@"_User"];
[query getObjectInBackgroundWithId:user.objectId block:^(LCObject *unauthenticatedUser, NSError *error) {
[unauthenticatedUser setObject:@"Toodle" forKey:@"username"];
[unauthenticatedUser saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
- // This will not succeed since the user is unauthenticated
+ // 无法执行,因为用户未鉴权
} else {
- // Failure is expected
+ // 操作失败
}
}];
}];
} else {
- // Error handling
+ // 错误处理
}
}];
} else {
- // Error handling
+ // 错误处理
}
}];
```
+```swift
+_ = LCUser.logIn(username: "Tom", password: "cat!@#123") { result in
+ switch result {
+ case .success(object: let user):
+ // 试图修改用户名
+ try! user.set("username", "Jerry")
+ // 密码已被加密,这样做会获取到空字符串
+ let password = user.get("password")
+ // 可以执行,因为用户已鉴权
+ user.save()
+
+ // 绕过鉴权直接获取用户
+ let query = LCQuery(className: "_User")
+ _ = query.get(user.objectId) { result in
+ switch result {
+ case .success(object: let unauthenticatedUser):
+ try! unauthenticatedUser.set("username", "Toodle")
+ _ = unauthenticatedUser.save { result in
+ switch result {
+ .success:
+ // 无法执行,因为用户未鉴权
+ .failure:
+ // 操作失败
+ }
+ }
+ case .failure(error: let error):
+ print(error)
+ }
+ }
+ case .failure(error: let error):
+ print(error)
+ }
+}
+```
+
+```dart
+try {
+ LCUser user = await LCUser.login('Tom', 'cat!@#123');
+ // 试图修改用户名
+ user['username'] = 'Jerry';
+ // 密码已被加密,这样做会获取到空字符串
+ String password = user['password'];
+ // 可以执行,因为用户已鉴权
+ await user.save();
+
+ // 绕过鉴权直接获取用户
+ LCQuery userQuery = LCQuery('_User');
+ LCUser unauthenticatedUser = await userQuery.get(user.objectId);
+ unauthenticatedUser['username'] = 'Toodle';
+
+ // 会出错,因为用户未鉴权
+ unauthenticatedUser.save();
+} on LCException catch (e) {
+ print('${e.code} : ${e.message}');
+}
+```
+
+```js
+const user = AV.User.logIn("Tom", "cat!@#123").then((user) => {
+ // 试图修改用户名
+ user.set("username", "Jerry");
+ // 密码已被加密,这样做会获取到空字符串
+ const password = user.get("password");
+ // 保存更改
+ user.save().then((user) => {
+ // 可以执行,因为用户已鉴权
+
+ // 绕过鉴权直接获取用户
+ const query = new AV.Query("_User");
+ query.get(user.objectId).then((unauthenticatedUser) => {
+ unauthenticatedUser.set("username", "Toodle");
+ unauthenticatedUser.save().then(
+ (unauthenticatedUser) => {},
+ (error) => {
+ // 会出错,因为用户未鉴权
+ }
+ );
+ });
+ });
+});
+```
+
+```python
+leancloud.User.login('Tom', 'cat!@#123')
+current_user = leancloud.User.get_current()
+
+# 试图修改用户名
+current_user.set('username', 'Jerry')
+# 密码已被加密,这样做会获取到空字符串
+password = current_user.get('password')
+# 可以执行,因为用户已鉴权
+current_user.save()
+
+# 绕过鉴权直接获取用户
+query = leancloud.Query('_User')
+unauthenticated_user = query.get(current_user.id)
+unauthenticated_user.set('username', 'Toodle')
+# 会出错,因为用户未鉴权
+unauthenticated_user.save()
+```
+
+```php
+User::logIn("Tom", "cat!@#123");
+$currentUser = User::getCurrentUser();
+
+// 试图修改用户名
+$currentUser->set("username", "Jerry");
+// 密码已被加密,这样做会获取到空字符串
+$password = $currentUser->get("password");
+// 可以执行,因为用户已鉴权
+$currentUser->save();
+
+// 绕过鉴权直接获取用户
+$query = new Query("_User");
+$unauthenticatedUser = $query->get($currentUser->getObjectId())
+$unauthenticatedUser->set("username", "Toodle");
+// 会出错,因为用户未鉴权
+$unauthenticatedUser->save()
+```
+
+```go
+user, err := client.Users.LogIn("Tom", "cat!@#123")
+if err != nil {
+ panic(err)
+}
+
+// 试图修改用户名,未鉴权将失败
+if err := client.User(user).Set("username", "Jerry"); err != nil {
+ panic(err)
+}
+
+// 密码已被加密,这样做会获取到空字符串
+password := user.String("password")
+
+// 可以执行,因为用户已鉴权
+if err := client.User(user).Set("username", "Jerry", leancloud.UseUser(user)); err != nil {
+ panic(err)
+}
+
+// 绕过鉴权直接获取用户
+unauthenticatedUser := User{}
+if err := client.Users.NewUserQuery().EqualTo("objectId", user.ID).First(&unauthenticatedUser); err != nil {
+ panic(err)
+}
+
+// 会出错,因为用户未鉴权
+if err := client.User(unauthenticatedUser).Set("username", "Toodle"); err != nil {
+ panic(err)
+}
+```
+
-The `LCUser` obtained from `TDSUser.GetCurrent()` will always be authenticated.
+通过调用 [当前用户](#当前用户) 相关方法获取的用户总是经过鉴权的。
+
+要查看一个用户对象是否经过鉴权,可以调用如下方法。通过经过鉴权的方法获取到的用户对象无需进行该检查。
+
+
+
+```cs
+IsAuthenticated
+```
+
+```java
+isAuthenticated
+```
+
+```objc
+isAuthenticatedWithSessionToken
+```
+
+```swift
+// 暂不支持
+```
+
+```dart
+isAuthenticated
+```
+
+```js
+isAuthenticated;
+```
+
+```python
+is_authenticated
+```
-To check if a `TDSUser` is authenticated, you can invoke the `isAuthenticated` method. You do not need to check if `TDSUser` is authenticated if it is obtained via an authenticated method.
+```php
+isAuthenticated
+```
-## Security of Other Objects
+```go
+// 暂不支持
+```
-For each given object, you can specify which users are allowed to read it and which are allowed to modify it. To support this type of security, each object has an access control list implemented by an `ACL` object. More details can be found in [ACL Guide](/sdk/storage/guide/acl/).
+
+注意,用户的密码只能在注册的时候进行设置,日后如需修改,只能通过 [重置密码](#重置密码) 的方式进行。密码不会被缓存在本地。如果尝试直接获取已登录用户的密码,会得到 `null`。
-## Third-Party Sign-on
+## 其他对象的安全
-We have already introduced how to [implement quick log-in with TapTap](/sdk/taptap-login/guide/start/#quick-log-in-with-taptap).
+对于给定的一个对象,可以指定哪些用户有权限读取或修改它。为实现该功能,每个对象都有一个由 `ACL` 对象组成的访问控制表。请参阅[ACL 权限管理开发指南](/sdk/storage/guide/acl/)。
-Besides TapTap, you can also use other services (like Apple, WeChat, and QQ and Facebook) to implement your account system. You can also associate existing accounts with these services so that the users can quickly log in with their existing accounts on these services.
+## 第三方账户登录
-Technically, we have implemented our interfaces in an open-ended manner. You can specify your own platform identifiers and authorization data, which means that our account system supports whatever third-party services you wish to connect to. For example, once you get the authorization data from Facebook, you can use `TDSUser.loginWithAuthData` to log the user in (you may set the platform name to be `facebook`).
+云服务支持应用层直接使用第三方社交平台(例如微信、微博、QQ 等)的账户信息来创建自己的账户体系并完成登录,也允许将既有账户与第三方账户绑定起来,这样终端用户后续可以直接用第三方账户信息来便捷登录。
-The code below shows how you can log a user in with WeChat:
+例如以下的代码展示了终端用户使用微信登录的处理流程:
```cs
Dictionary thirdPartyData = new Dictionary {
- // Optional
+ // 必须
{ "openid", "OPENID" },
{ "access_token", "ACCESS_TOKEN" },
{ "expires_in", 7200 },
+
+ // 可选
{ "refresh_token", "REFRESH_TOKEN" },
{ "scope", "SCOPE" }
};
-TDSUser currentUser = await TDSUser.LoginWithAuthData(thirdPartyData, "weixin");
+LCUser currentUser = await LCUser.LoginWithAuthData(thirdPartyData, "weixin");
```
```java
Map thirdPartyData = new HashMap();
-// Optional
+// 必须
thirdPartyData.put("expires_in", 7200);
thirdPartyData.put("openid", "OPENID");
thirdPartyData.put("access_token", "ACCESS_TOKEN");
+// 可选
thirdPartyData.put("refresh_token", "REFRESH_TOKEN");
thirdPartyData.put("scope", "SCOPE");
-TDSUser.loginWithAuthData(TDSUser.class, thirdPartyData, "weixin").subscribe(new Observer() {
+LCUser.loginWithAuthData(thirdPartyData, "weixin").subscribe(new Observer() {
public void onSubscribe(Disposable disposable) {
}
- public void onNext(TDSUser user) {
- System.out.println("Logged in.");
+ public void onNext(LCUser user) {
+ System.out.println("成功登录");
}
public void onError(Throwable throwable) {
- System.out.println("An error occurred.");
+ System.out.println("尝试使用第三方账号登录,发生错误。");
}
public void onComplete() {
}
@@ -516,33 +2337,104 @@ TDSUser.loginWithAuthData(TDSUser.class, thirdPartyData, "weixin").subscribe(new
```objc
NSDictionary *thirdPartyData = @{
- // Optional
+ // 必须
@"openid":@"OPENID",
@"access_token":@"ACCESS_TOKEN",
@"expires_in":@7200,
+
+ // 可选
@"refresh_token":@"REFRESH_TOKEN",
@"scope":@"SCOPE",
};
-TDSUser *user = [TDSUser user];
+LCUser *user = [LCUser user];
LCUserAuthDataLoginOption *option = [LCUserAuthDataLoginOption new];
option.platform = LeanCloudSocialPlatformWeiXin;
[user loginWithAuthData:thirdPartyData platformId:LeanCloudSocialPlatformWeiXin options:option callback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
- NSLog(@"Logged in.");
- } else {
- NSLog(@"An error occurred: %@",error.localizedFailureReason);
+ NSLog(@"登录成功");
+ }else{
+ NSLog(@"登录失败:%@",error.localizedFailureReason);
}
}];
```
+```swift
+let thirdPartyData: [String: Any] = [
+ // 必须
+ "openid": "OPENID",
+ "access_token": "ACCESS_TOKEN",
+ "expires_in": 7200,
+
+ // 可选
+ "refresh_token": "REFRESH_TOKEN",
+ "scope": "SCOPE"
+]
+let user = LCUser()
+user.logIn(authData: thirdPartyData, platform: .weixin) { (result) in
+ switch result {
+ case .success:
+ assert(user.objectId != nil)
+ case .failure(error: let error):
+ print(error)
+ }
+}
+```
+
+```dart
+var thirdPartyData = {
+ // 必须
+ 'openid': 'OPENID',
+ 'access_token': 'ACCESS_TOKEN',
+ 'expires_in': 7200,
+
+ // 可选
+ 'refresh_token': 'REFRESH_TOKEN',
+ 'scope': 'SCOPE'
+};
+LCUser currentUser = await LCUser.loginWithAuthData(thirdPartyData, 'weixin');
+```
+
+```js
+const thirdPartyData = {
+ // 必须
+ openid: "OPENID",
+ access_token: "ACCESS_TOKEN",
+ expires_in: 7200,
+
+ // 可选
+ refresh_token: "REFRESH_TOKEN",
+ scope: "SCOPE",
+};
+AV.User.loginWithAuthData(thirdPartyData, "weixin").then(
+ (user) => {
+ // 登录成功
+ },
+ (error) => {
+ // 登录失败
+ }
+);
+```
+
+```python
+# 暂不支持
+```
+
+```php
+// 暂不支持
+```
+
+```go
+// 暂不支持
+```
+
-`loginWithAuthData` requires two arguments to locate a unique account:
+`loginWithAuthData` 系列方法需要两个参数来唯一确定一个账户:
-- The name of the third-party platform, which is `weixin` in the example above. You can decide this name on your own.
-- The authorization data from the third-party platform, which is the `thirdPartyData` in the example above (depending on the platform, it usually includes `uid`, `access_token`, and `expires_in`).
+- 第三方平台的名字,就是前例中的 `weixin`,该名字由应用层自己决定。
+- 第三方平台的授权信息,就是前例中的 `thirdPartyData`(一般包括 `uid`、`access_token`、`expires_in` 等信息,与具体的第三方平台有关)。
-The cloud will then verifies that the provided `authData` is valid and checks if a user is already associated with it. If so, it returns the status code `200 OK` along with the details (including a [`sessionToken`](#setting-the-current-user) of the user). If the `authData` is not linked to any accounts, you will instead receive the status code `201 Created`, indicating that a new user has been created. The body of the response contains `objectId`, `createdAt`, `sessionToken`, and an automatically-generated unique `username`. For example:
+云端会使用第三方平台的鉴权信息来查询是否已经存在与之关联的账户。如果存在的话,则返回 `200 OK` 状态码,同时附上用户的信息(包括 [`sessionToken`](#设置当前用户))。如果第三方平台的信息没有和任何账户关联,客户端会收到 `201 Created` 状态码,意味着新账户被创建,同时附上用户的 `objectId`、`createdAt`、`sessionToken` 和一个自动生成的 `username`,例如:
```json
{
@@ -551,7 +2443,7 @@ The cloud will then verifies that the provided `authData` is valid and checks if
"createdAt": "2018-05-21T09:33:26.406Z",
"updatedAt": "2018-05-21T09:33:26.575Z",
"sessionToken": "…",
- // authData won't be returned in most cases; see explanations below
+ // authData 通常不会返回,继续阅读以了解其中原因
"authData": {
"weixin": {
"openid": "OPENID",
@@ -565,94 +2457,139 @@ The cloud will then verifies that the provided `authData` is valid and checks if
}
```
-Now we will see a new record showing up in the `_User` table that has an `authData` field. Within this field is the authorization data from the third-party platform. For security reasons, the `authData` field won’t be returned to the client unless the current user owns it.
+这时候我们会看到 `_User` 表中出现了一条新的账户记录,账户中有一个名为 `authData` 的列,保存了第三方平台的授权信息。出于安全考虑,`authData` 不会被返回给客户端,除非它属于当前用户。
-You will need to implement the authentication process involving the third-party platform yourself (usually with OAuth 1.0 or 2.0) to obtain the authentication data, which will be used to log a user in.
+开发者需要自己完成第三方平台的鉴权流程(一般通过 OAuth 1.0 或 2.0),以获取鉴权信息,继而到云端来登录。
### Sign in with Apple
-If you plan to implement [Sign in with Apple](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api), the cloud can help you verify `identityToken`s and obtain `access_token`s from Apple. Below is the structure of `authData` for Sign in with Apple:
+如果你需要开发 [Sign in with Apple](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api),云服务可以帮你校验 `identityToken`,并获取 Apple 的 `access_token`。Apple Sign In 的 `authData` 结构如下:
```json
{
"lc_apple": {
- "uid": "The User Identifier obtained from Apple",
- "identity_token": "The identityToken obtained from Apple",
- "code": "The Authorization Code obtained from Apple"
+ "uid": "从 Apple 获取到的 User Identifier",
+ "identity_token": "从 Apple 获取到的 identityToken",
+ "code": "从 Apple 获取到的 Authorization Code"
}
}
```
-Each `authData` has the following fields:
+`authData` 中的 key 的作用:
-- **`lc_apple`**: The cloud will run the logic related to `identity_token` and `code` only when the platform name is `lc_apple`.
-- **`uid`**: Required. The cloud tells if the user exists with `uid`.
-- **`identity_token`**: Optional. The cloud will automatically validate `identity_token` if this field exists. Please make sure you have provided relevant information on **Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings > Third-party accounts**.
-- **`code`**: Optional. The cloud will automatically obtain `access_token` and `refresh_token` from Apple if this field exists. Please make sure you have provided relevant information on **Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings > Third-party accounts**.
+- **`lc_apple`**:只有 platform 为 `lc_apple` 时,云服务才会执行 `identity_token` 和 `code` 的逻辑。
+- **`uid`**:必填。云服务通过 `uid` 判断是否存在用户。
+- **`identity_token`**:可选。`authData` 中有 `identity_token` 时云端会自动校验 `identity_token` 的有效性。开发者需要在 **云服务控制台 > 内建账户 > 设置 > 第三方集成** 中填写 Apple 的相关信息。
+- **`code`**:可选。`authData` 中有 `code` 时云端会自动用该 `code` 向 Apple 换取 `access_token` 和 `refresh_token`。开发者需要在 **云服务控制台 > 内建账户 > 设置 > 第三方集成** 中填写 Apple 的相关信息。
-#### Getting Client ID
+#### 获取 Client ID
-Client ID is used to verify `identity_token` and to obtain `access_token`. It is the identifier of an Apple app (`AppID` or `serviceID`). For native apps, it is the Bundle Identifier in Xcode, which looks like `com.mytest.app`. See [Apple’s docs](https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens) for more details.
+Client ID 用于校验 `identity_token` 及获取 `access_token`,指的是 Apple 应用的 identifier,也就是 `AppID` 或 `serviceID`。对于原生应用来说,指的是 Xcode 中的 Bundle Identifier,例如 `com.mytest.app`。详情请参考 [Apple 的文档](https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens)。
-#### Getting Private Key and Private Key ID
+#### 获取 Private Key 及 Private Key ID
-Private Key is used to obtain `access_token`. You can go to Apple Developer, select “Keys” from “Certificates, Identifiers & Profiles”, add a Private Key for Sign in with Apple, and then download the `.p8` file. You will also obtain the Private Key ID from the page you download the key. See [Apple’s docs](https://developer.apple.com/cn/help/account/) for more details.
+Private Key 用于获取 `access_token`。登录 Apple 开发者平台,在左侧的「Certificates, Identifiers & Profiles」中选择「Keys」,添加一个用于 Apple Sign In 的 Private Key,下载 `.p8` 文件,同时在下载 Key 的页面获得 Private Key ID。详情请参考 [Apple 的文档](https://help.apple.com/developer-account/#/dev77c875b7e)。
-The last step is to fill in the Key ID on the Developer Center and upload the downloaded Private Key. You can only upload Private Keys, but cannot view or download them.
+将 Key ID 填写到控制台,将下载下来的 Private Key 文件上传到控制台。控制台只能上传 Private Key 文件,无法查看及下载其内容。
-#### Getting Team ID
+#### 获取 Team ID
-Team ID is used to obtain `access_token`. You can view your team’s Team ID by going to Apple Developer and looking at the top-right corner or the Membership page. Make sure to select the team matching the selected Bundle ID.
+Team ID 用于获取 `access_token`。登录 Apple 开发者平台,在右上角或 Membership 页面即可看到自己所属开发团队的 Team ID。注意选择 Bundle ID 对应的 Team。
-#### Logging in to Cloud Services With Sign in with Apple
+#### 使用 Apple Sign In 登录云服务
-After you have filled in all the information on the Developer Center, you can log a user in with the following code:
+在控制台填写完成所有信息后,使用以下代码登录。
```cs
Dictionary appleAuthData = new Dictionary {
- // Required
+ // 必须
{ "uid", "USER IDENTIFIER" },
- // Optional
+ // 可选
{ "identity_token", "IDENTITY TOKEN" },
{ "code", "AUTHORIZATION CODE" }
};
-TDSUser currentUser = await TDSUser.LoginWithAuthData(appleAuthData, "lc_apple");
+LCUser currentUser = await LCUser.LoginWithAuthData(appleAuthData, "lc_apple");
```
```java
-// Not supported yet
+// 不支持
```
```objc
NSDictionary *appleAuthData = @{
- // Required
+ // 必须
@"uid":@"USER IDENTIFIER",
- // Optional
+ // 可选
@"identity_token":@"IDENTITY TOKEN",
@"code":@"AUTHORIZATION CODE",
};
-TDSUser *user = [TDSUser user];
+LCUser *user = [LCUser user];
[user loginWithAuthData:appleAuthData platformId:"lc_apple" options:nil callback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
- NSLog(@"Logged in.");
+ NSLog(@"登录成功");
}else{
- NSLog(@"Failed to log in: %@",error.localizedFailureReason);
+ NSLog(@"登录失败:%@",error.localizedFailureReason);
}
}];
```
-
+```swift
+let appleData: [String: Any] = [
+ // 必须
+ "uid": "USER IDENTIFIER",
+ // 可选
+ "identity_token": "IDENTITY TOKEN",
+ "code": "AUTHORIZATION CODE"
+]
+let user = LCUser()
+user.logIn(authData: appleData, platform: .apple) { (result) in
+ switch result {
+ case .success:
+ assert(user.objectId != nil)
+ case .failure(error: let error):
+ print(error)
+ }
+}
+
+```
+
+```dart
+var appleData = {
+ // 必须
+ "uid": "USER IDENTIFIER",
+ // 可选
+ "identity_token": "IDENTITY TOKEN",
+ "code": "AUTHORIZATION CODE"
+};
+LCUser currentUser = await LCUser.loginWithAuthData(appleData, 'lc_apple');
+```
+
+```js
+// 不支持
+```
+
+```python
+# 不支持
+```
+
+```php
+// 不支持
+```
+
+```go
+// 不支持
+```
-### Storing Authentication Data
+
-The `authData` of each user is a JSON object with platform names as keys and authentication data as values.
+### 鉴权数据的保存
-
+每个用户的 `authData` 是一个以平台名为键名,鉴权信息为键值的 JSON 对象。
-A user associated with a WeChat account will have the following object as its `authData`:
+一个关联了微信账户的用户应该会有下列对象作为 `authData`:
```json
{
@@ -666,7 +2603,7 @@ A user associated with a WeChat account will have the following object as its `a
}
```
-A user associated with a Weibo account will have the following object as its `authData`:
+而一个关联了微博账户的用户,则会有如下的 `authData`:
```json
{
@@ -679,7 +2616,7 @@ A user associated with a Weibo account will have the following object as its `au
}
```
-A user can be associated with multiple third-party platforms. If a user is associated with both WeChat and Weibo, their `authData` may look like this:
+我们允许一个账户绑定多个第三方平台的鉴权数据,这样如果某个用户同时关联了微信和微博账户,则其 `authData` 可能会是这样的:
```json
{
@@ -699,9 +2636,7 @@ A user can be associated with multiple third-party platforms. If a user is assoc
}
```
-
-
-It’s important to understand the data structure of `authData`. When a user logs in with the following authentication data:
+理解 `authData` 的数据结构至关重要。一个终端用户通过如下的鉴权信息来登录的时候,
```json
"platform": {
@@ -713,29 +2648,29 @@ It’s important to understand the data structure of `authData`. When a user log
}
```
-The cloud will first look at the account system to see if there is an account that has its `authData.platform.openid` to be the `OPENID`. If there is, return the existing account. If not, create a new account and write the authentication data into the `authData` field of this new account, and then return the new account’s data as the result.
+云端首先会查找账户系统,看看是否存在 `authData.platform.openid` 等于 `OPENID` 的账户,如果存在,则返回现有账户,如果不存在那么就创建一个新账户,同时将上面的鉴权信息写入新账户的 `authData` 属性中,并将新账户的数据当成结果返回。
-The cloud will automatically create a unique index for the `authData..` of each user, which prevents the formation of duplicate data.
-For some of the platforms specially supported by us, `` refers to the `openid` field. For others (the other platforms specially supported by us, and those not specially supported by us), it refers to the `uid` field.
+云端会自动为每个用户的 `authData..` 创建唯一索引,从而避免重复数据。
+`` 在微信等部分云服务内建支持的第三方平台上为 `openid` 字段,在其他第三方平台(包括部分云服务专门支持的第三方平台和所有云服务没有专门支持的第三方平台)上为 `uid` 字段。
-### Automatically Validating Third-Party Authorization Data
+### 自动验证第三方平台授权信息
-The cloud can automatically validate access tokens for certain platforms, which prevents counterfeit account data from entering your app’s account system. If the validation fails, the cloud will return the `invalid authData` error, and the association will not be created. For those services that are not recognized by the cloud, you need to validate the access tokens yourself.
-You can validate access tokens when a user signs up or logs in by using LeanEngine’s `beforeSave` hook and `beforeUpdate` hook.
+为了确保账户数据的有效性,云端还支持对部分平台的 Access Token 的有效性进行自动验证,以防止伪造账户数据。如果有效性验证不通过,云端会返回 `invalid authData` 错误,关联不会被建立。对于云端无法识别的服务,开发者需要自己去验证 Access Token 的有效性。
+比如,注册、登录时分别通过云引擎的 `beforeSave` hook、`beforeUpdate` hook 来验证 Access Token 有效性。
-To enable the feature, please configure the platforms’ **App IDs** and **Secret Keys** on **Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings**.
+如果希望使用这一功能,则在开始使用前,需要在 **云服务控制台 > 内建账户 > 设置** 配置相应平台的 **应用 ID** 和 **应用 Secret Key**。
-To disable the feature, please uncheck **Validate access tokens when logging in with third-party accounts** on **Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings**.
+如果不希望云端自动验证 Access Token,可以在 **云服务控制台 > 内建账户 > 设置** 里面取消勾选 **第三方登录时,验证用户 AccessToken 合法性**。
-The reason for configuring the platforms is that when a `TDSUser` is created, the cloud will use the relevant data to validate the `thirdPartyData` to ensure that the `TDSUser` matches a real user, which ensures the security of your app.
+配置平台账号的目的在于创建用户对象时,云端会使用相关信息去校验请求参数 `thirdPartyData` 的合法性,确保用户对象实际对应着一个合法真实的用户,确保平台安全性。
-### Linking Third-Party Accounts
+### 绑定第三方账户
-If a user is already logged in, you can link third-party accounts to this user. For example, if a user first logs in as a guest and then links their TapTap or other third-party accounts, the user will be able to access the same account when they log in with the same TapTap or third-party accounts in the future.
+如果用户已经登录,也可以在当前账户上绑定或解绑更多第三方平台信息。
-After a user links their third-party account, the account information will be added to the `authData` field of the corresponding `TDSUser`.
+绑定成功后,新的第三方账户信息会被添加到用户对象的 `authData` 字段里。
-The following code links a WeChat account to a user:
+例如,下面的代码可以关联微信账户:
@@ -744,64 +2679,257 @@ await currentUser.AssociateAuthData(weixinData, "weixin");
```
```java
-user.associateWithAuthData(weixinData, "weixin").subscribe(new Observer() {
+user.associateWithAuthData(weixinData, "weixin").subscribe(new Observer() {
+ @Override
+ public void onSubscribe(Disposable d) {
+ }
+ @Override
+ public void onNext(LCUser user) {
+ System.out.println("绑定成功");
+ }
+ @Override
+ public void onError(Throwable e) {
+ System.out.println("绑定失败:" + e.getMessage());
+ }
+ @Override
+ public void onComplete() {
+ }
+});
+```
+
+```objc
+[user associateWithAuthData:weixinData platformId:LeanCloudSocialPlatformWeiXin options:nil callback:^(BOOL succeeded, NSError * _Nullable error) {
+ if (succeeded) {
+ NSLog(@"成功");
+ } else{
+ NSLog(@"失败:%@",error.localizedFailureReason);
+ }
+}];
+```
+
+```swift
+currentUser.associate(authData: weixinData, platform: .weixin) { (result) in
+ switch result {
+ case .success:
+ // 关联成功
+ case .failure(error: let error):
+ // 关联失败
+ }
+}
+```
+
+```dart
+await currentUser.associateAuthData(weixinData, 'weixin');
+```
+
+```js
+user
+ .associateWithAuthData(weixinData, "weixin")
+ .then(function (user) {
+ // 成功绑定
+ })
+ .catch(function (error) {
+ console.error("error: ", error);
+ });
+```
+
+```python
+user.link_with("weixin", weixin_data)
+```
+
+```php
+$user->linkWith("weixin", $weixinData);
+```
+
+```go
+// 暂不支持
+```
+
+
+
+为节省篇幅,上面的代码示例中没有给出具体的平台授权信息,相关内容请参考上面的[「第三方账户登录」](#第三方账户登录)一节。
+
+### 解除与第三方账户的关联
+
+类似地,可以解绑第三方账户。
+
+例如,下面的代码可以解除用户和微信账户的关联:
+
+
+
+```cs
+LCUser currentUser = await LCUser.GetCurrent();
+await currentUser.DisassociateWithAuthData("weixin");
+```
+
+```java
+LCUser user = LCUser.currentUser();
+user.dissociateWithAuthData("weixin").subscribe(new Observer() {
+ @Override
+ public void onSubscribe(Disposable d) {
+ }
+ @Override
+ public void onNext(LCUser user) {
+ System.out.println("解绑成功");
+ }
+ @Override
+ public void onError(Throwable e) {
+ System.out.println("解绑失败:" + e.getMessage());
+ }
+ @Override
+ public void onComplete() {
+ }
+});
+```
+
+```objc
+[user disassociateWithPlatform:LeanCloudSocialPlatformWeiXin callback:^(BOOL succeeded, NSError * _Nullable error) {
+ if (succeeded) {
+ NSLog(@"成功");
+ } else{
+ NSLog(@"失败:%@",error.localizedFailureReason);
+ }
+}];
+```
+
+```swift
+currentUser.disassociate(authData: .weixin) { (result) in
+ switch result {
+ case .success:
+ // 解除关联成功
+ case .failure(error: let error):
+ // 解除关联失败
+ }
+}
+```
+
+```dart
+LCUser currentUser = await LCUser.getCurrent();
+await currentUser.disassociateWithAuthData('weixin');
+```
+
+```js
+user.dissociateAuthData("weixin").then(
+ (s) => {
+ // 解除关联成功
+ },
+ (error) => {
+ // 解除关联失败
+ }
+);
+```
+
+```python
+user.unlink_from("weixin")
+```
+
+```php
+$user->unlinkWith("weixin");
+```
+
+```go
+// 暂不支持
+```
+
+
+
+
+
+扩展:第三方登录时补充完整的用户信息
+
+有些产品,新用户在使用第三方账号授权拿到相关信息后,仍然需要补充设置用户名、手机号、密码等重要信息后,才被允许登录成功。
+
+这时要使用 `loginWithauthData` 登录接口的 `failOnNotExist` 参数并将其设置为 `true`。服务端会判断是否已存在能匹配上的 `authData`,如果不存在则会返回 `211` 错误码和 `Could not find user` 报错信息。开发者根据这个 `211` 错误码,跳转到要求输入用户名、密码、手机号等信息的页面,实例化一个用户对象,保存上述补充数据,再次调用 `loginWithauthData` 接口进行登录,并 **不再传入 `failOnNotExist` 参数**。示例代码如下:
+
+
+
+```cs
+try {
+ Dictionary thirdPartyData = new Dictionary {
+ // 必须
+ { "openid", "OPENID" },
+ { "access_token", "ACCESS_TOKEN" },
+ { "expires_in", 7200 },
+
+ // 可选
+ { "refresh_token", "REFRESH_TOKEN" },
+ { "scope", "SCOPE" }
+ };
+ LCUserAuthDataLoginOption option = new LCUserAuthDataLoginOption();
+ option.FailOnNotExist = true;
+ LCUser currentUser = await LCUser.LoginWithAuthData(thirdPartyData, "weixin", option: option);
+} catch (LCException e) {
+ if (e.code == 211) {
+ // 不存在 authData 的 LCUser 的实例,跳转到输入用户名、密码、手机号等业务页面
+ }
+}
+
+// 跳转到输入用户名、密码、手机号等业务页面之后
+Dictionary thirdPartyData = new Dictionary {
+ { "expires_in", 7200 },
+ { "openid", "OPENID" },
+ { "access_token", "ACCESS_TOKEN" }
+};
+try {
+ LCUserAuthDataLoginOption option = new LCUserAuthDataLoginOption();
+ option.FailOnNotExist = true;
+ LCUser user = await LCUser.LoginWithAuthData(thirdPartyData, "weixin", option: option);
+ user.Username = "Tom";
+ user.Mobile = "+8618200008888";
+ await user.Save();
+} catch (LCException e) {
+ //其他报错信息
+}
+```
+
+```java
+Map thirdPartyData = new HashMap();
+thirdPartyData.put("expires_in", 7200);
+thirdPartyData.put("openid", "OPENID");
+thirdPartyData.put("access_token", "ACCESS_TOKEN");
+thirdPartyData.put("refresh_token", "REFRESH_TOKEN");
+thirdPartyData.put("scope", "SCOPE");
+Boolean failOnNotExist = true;
+LCUser user = new LCUser();
+user.loginWithAuthData(thirdPartyData, "weixin", failOnNotExist).subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
- public void onNext(TDSUser user) {
- System.out.println("Account linked.");
+ public void onNext(LCUser user) {
+ System.out.println("存在匹配的用户,登录成功");
}
@Override
public void onError(Throwable e) {
- System.out.println("Failed to link the account: " + e.getMessage());
+ LCException avException = new LCException(e);
+ int code = avException.getCode();
+ if (code == 211){
+ // 跳转到输入用户名、密码、手机号等业务页面
+ } else {
+ System.out.println("发生错误:" + e.getMessage());
+ }
}
@Override
public void onComplete() {
}
});
-```
-
-```objc
-[user associateWithAuthData:weixinData platformId:LeanCloudSocialPlatformWeiXin options:nil callback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- NSLog(@"Account linked.");
- } else{
- NSLog(@"Failed to link the account: %@",error.localizedFailureReason);
- }
-}];
-```
-
-
-
-The code above omitted the authorization data of the platform. See [Third-Party Sign-on](#third-party-sign-on) for more details.
-
-### Unlinking
-
-Similarly, a third-party account can be unlinked.
-For example, the code below unlinks a user’s WeChat account:
-
-
-
-```cs
-TDSUser currentUser = await TDSUser.GetCurrent();
-await currentUser.DisassociateWithAuthData("weixin");
-```
-
-```java
-TDSUser user = TDSUser.currentUser();
-user.dissociateWithAuthData("weixin").subscribe(new Observer() {
+// 跳转到输入用户名、密码、手机号等业务页面之后
+LCUser user = new LCUser();
+user.setUsername("Tom");
+user.setMobilePhoneNumber("+8618200008888");
+Boolean failOnNotExist = false;
+user.loginWithAuthData(thirdPartyData, "weixin", failOnNotExist).subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
- public void onNext(TDSUser user) {
- System.out.println("Unlinked.");
+ public void onNext(LCUser user) {
+ System.out.println("登录成功");
}
@Override
public void onError(Throwable e) {
- System.out.println("Failed to unlink: " + e.getMessage());
+ System.out.println("登录失败:" + e.getMessage());
}
@Override
public void onComplete() {
@@ -810,18 +2938,164 @@ user.dissociateWithAuthData("weixin").subscribe(new Observer() {
```
```objc
-[user disassociateWithPlatform:LeanCloudSocialPlatformWeiXin callback:^(BOOL succeeded, NSError * _Nullable error) {
+NSDictionary *thirdPartyData = @{
+ @"access_token":@"ACCESS_TOKEN",
+ @"expires_in":@7200,
+ @"refresh_token":@"REFRESH_TOKEN",
+ @"openid":@"OPENID",
+ @"scope":@"SCOPE",
+ };
+LCUser *user = [LCUser user];
+LCUserAuthDataLoginOption *option = [LCUserAuthDataLoginOption new];
+option.platform = LeanCloudSocialPlatformWeiXin;
+option.failOnNotExist = true;
+[user loginWithAuthData:thirdPartyData platformId:LeanCloudSocialPlatformWeiXin options:option callback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
- NSLog(@"Unlinked.");
- } else{
- NSLog(@"Failed to unlink: %@",error.localizedFailureReason);
+ // 你的逻辑
+ } else if ([error.domain isEqualToString:kLeanCloudErrorDomain] && error.code == 211) {
+ // 不存在 thirdPartyData 的 LCUser 的实例,跳转到输入用户名、密码、手机号等业务页面
+ }
+}];
+
+// 跳转到输入用户名、密码、手机号等业务页面之后
+LCUser *user = [LCUser user];
+user.username = @"Tom";
+user.mobilePhoneNumber = @"+8618200008888";
+LCUserAuthDataLoginOption *option = [LCUserAuthDataLoginOption new];
+option.platform = LeanCloudSocialPlatformWeiXin;
+[user loginWithAuthData:thirdPartyData platformId:LeanCloudSocialPlatformWeiXin options:option callback:^(BOOL succeeded, NSError * _Nullable error) {
+ if (succeeded) {
+ NSLog(@"登录成功");
+ }else{
+ NSLog(@"登录失败:%@",error.localizedFailureReason);
}
}];
```
+```swift
+let thirdPartyData: [String: Any] = [
+ "access_token": "ACCESS_TOKEN",
+ "expires_in": 7200,
+ "refresh_token": "REFRESH_TOKEN",
+ "openid": "OPENID",
+ "scope": "SCOPE"
+]
+let user = LCUser()
+user.logIn(authData: thirdPartyData, platform: .weixin, options: [.failOnNotExist]) { (result) in
+ switch result {
+ case .success:
+ assert(user.objectId != nil)
+ case .failure(error: let error):
+ if error.code == 211 {
+ // 不存在绑定了当前 authData 的 User 的实例
+ // 跳转到输入用户名、密码、手机号等业务页面
+ let user = LCUser()
+ user.username = "Tom"
+ user.password = "cat!@#123"
+ user.mobilePhoneNumber = "+8618200008888"
+ user.logIn(authData: thirdPartyData, platform: .weixin, completion: { (result) in
+ switch result {
+ case .success:
+ assert(user.objectId != nil)
+ case .failure(error: let error):
+ print(error)
+ }
+ })
+ }
+ }
+}
+```
+
+```dart
+try {
+ Map thirdPartyData = {
+ // 必须
+ 'openid': 'OPENID',
+ 'access_token': 'ACCESS_TOKEN',
+ 'expires_in': 7200,
+
+ // 可选
+ 'refresh_token': 'REFRESH_TOKEN',
+ 'scope': 'SCOPE'
+ };
+ LCUserAuthDataLoginOption option = new LCUserAuthDataLoginOption();
+ option.failOnNotExist = true;
+ LCUser currentUser = await LCUser.loginWithAuthData(thirdPartyData, 'weixin', option: option);
+} on LCException catch (e) {
+ if (e.code == 211) {
+ // 不存在 authData 的 LCUser 的实例,跳转到输入用户名、密码、手机号等业务页面
+ }
+}
+
+// 跳转到输入用户名、密码、手机号等业务页面之后
+Map thirdPartyData = {
+ 'expires_in': 7200,
+ 'openid': 'OPENID',
+ 'access_token': 'ACCESS_TOKEN'
+};
+try {
+ LCUserAuthDataLoginOption option = new LCUserAuthDataLoginOption();
+ option.failOnNotExist = true;
+ LCUser user = await LCUser.loginWithAuthData(thirdPartyData, 'weixin', option: option);
+ user.username = 'Tome';
+ user.mobile = '+8618200008888';
+ await user.save();
+} on LCException catch (e) {
+ //其他报错信息
+}
+```
+
+```js
+const thirdPartyData = {
+ access_token: "ACCESS_TOKEN",
+ expires_in: 7200,
+ refresh_token: "REFRESH_TOKEN",
+ openid: "OPENID",
+ scope: "SCOPE",
+};
+AV.User.loginWithAuthData(thirdPartyData, "weixin", {
+ failOnNotExist: true,
+}).then(
+ (s) => {
+ // 登录成功
+ },
+ (error) => {
+ // 登录失败
+ // 检查 error.code == 211,跳转到用户名、手机号等资料的输入页面
+ }
+);
+
+const user = new AV.User();
+// 设置用户名
+user.setUsername("Tom");
+// 设置密码
+user.setMobilePhoneNumber("+8618200008888");
+user.setPassword("cat!@#123");
+// 设置邮箱
+user.setEmail("tom@leancloud.rocks");
+user.loginWithAuthData(thirdPartyData, "weixin").then(
+ (loggedInUser) => {
+ console.log(loggedInUser);
+ },
+ (error) => {}
+);
+```
+
+```python
+# 暂不支持
+```
+
+```php
+// 暂不支持
+```
+
+```go
+// 暂不支持
+```
+
-
+
@@ -881,7 +3155,7 @@ Dictionary thirdPartyData = new Dictionary {
LCUserAuthDataLoginOption option = new LCUserAuthDataLoginOption();
option.AsMainAccount = true;
option.UnionIdPlatform = "weixin";
-TDSUser currentUser = await TDSUser.LoginWithAuthDataAndUnionId(
+LCUser currentUser = await LCUser.LoginWithAuthDataAndUnionId(
thirdPartyData, "wxleanoffice", "unionid4a",
option: option);
```
@@ -892,15 +3166,15 @@ thirdPartyData.put("expires_in", 1384686496);
thirdPartyData.put("uid", "officeopenid");
thirdPartyData.put("access_token", "officetoken");
thirdPartyData.put("scope", "SCOPE");
-TDSUser.loginWithAuthData(TDSUser.class, thirdPartyData, "wxleanoffice",
+LCUser.loginWithAuthData(LCUser.class, thirdPartyData, "wxleanoffice",
"unionid4a", "weixin", true) // 新增参数,分别表示 uniondId,unionIdPlatform,asMainAccount
// 对于 unionIdPlatform,这里使用「weixin」来指代微信平台。
- .subscribe(new Observer() {
+ .subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
- public void onNext(TDSUser user) {
+ public void onNext(LCUser user) {
System.out.println("登录成功");
}
@Override
@@ -921,7 +3195,7 @@ NSDictionary *thirdPartyData = @{
@"scope":@"SCOPE",
@"unionid":@"unionid4a" // 新增属性
};
-TDSUser *currentuser = [TDSUser user];
+LCUser *currentuser = [LCUser user];
LCUserAuthDataLoginOption *option = [LCUserAuthDataLoginOption new];
option.platform = @"weixin"; // 这里指定 UnionIdPlatform,使用「weixin」来指代微信平台。
option.unionId = thirdPartyData[@"unionid"];
@@ -935,9 +3209,92 @@ option.isMainAccount = true;
}];
```
+```swift
+let thirdPartyData: [String: Any] = [
+ "access_token": "officetoken",
+ "expires_in": 1384686496,
+ "uid": "officeopenid",
+ "scope": "SCOPE",
+ "unionid": "unionid4a" // 新增属性
+]
+let user = LCUser()
+user.logIn(
+ authData: thirdPartyData,
+ platform: .custom("wxleanoffice"),
+ unionID: thirdPartyData["unionid"] as? String,
+ unionIDPlatform: .weixin, // 这里指定 UnionIdPlatform,使用「weixin」来指代微信平台。
+ options: [.mainAccount])
+{ (result) in
+ switch result {
+ case .success:
+ assert(user.objectId != nil)
+ case .failure(error: let error):
+ print(error)
+ }
+}
+```
+
+```dart
+Map thirdPartyData = {
+ // 必须
+ 'uid': 'officeopenid',
+ 'access_token': 'officetoken',
+ 'expires_in': 1384686496,
+ 'unionId': 'unionid4a', // 新增属性
+
+ // 可选
+ 'refresh_token': '...',
+ 'scope': 'SCOPE'
+};
+LCUserAuthDataLoginOption option = LCUserAuthDataLoginOption();
+option.asMainAccount = true;
+option.unionIdPlatform = 'weixin';
+LCUser currentUser = await LCUser.loginWithAuthDataAndUnionId(
+ thirdPartyData, 'wxleanoffice', 'unionid4a',
+ option: option);
+```
+
+```js
+const thirdPartyData = {
+ access_token: "officetoken",
+ expires_in: 1384686496,
+ uid: "officeopenid",
+ scope: "SCOPE",
+};
+
+AV.User.loginWithAuthDataAndUnionId(
+ thirdPartyData,
+ "wxleanoffice",
+ "unionid4a", // 新增参数
+ {
+ unionIdPlatform: "weixin", // 这里指定 UnionIdPlatform,使用「weixin」来指代微信平台。
+ asMainAccount: true,
+ }
+).then(
+ (user) => {
+ // 绑定成功
+ },
+ (error) => {
+ // 绑定失败
+ }
+);
+```
+
+```python
+# 暂不支持
+```
+
+```php
+// 暂不支持
+```
+
+```go
+// 暂不支持
+```
+
-注意代码中将微信传回来的 openid 属性改为了 uid,这是因为云端要求对于自定义的 platform,只能使用 uid 这样的属性名,才能保证自动建立 `authData..uid` 的唯一索引,具体可以参考[数据存储 REST API 使用详解](/sdk/storage/guide/rest/)的《连接用户账户和第三方平台》一节。
+注意代码中将微信传回来的 openid 属性改为了 uid,这是因为云端要求对于自定义的 platform,只能使用 uid 这样的属性名,才能保证自动建立 `authData..uid` 的唯一索引,具体可以参考《数据存储 REST API 使用详解》的[《连接用户账户和第三方平台》](/sdk/authentication/rest/#连接用户账户和第三方平台)一节。
如果用户 A 是第一次在「云服务通讯」中通过微信登录,那么 `_User` 表中会增加一个新用户(假设其 `objectId` 为 `ThisIsUserA`),其 `authData` 的结果如下:
@@ -983,7 +3340,7 @@ Dictionary thirdPartyData = new Dictionary {
LCUserAuthDataLoginOption option = new LCUserAuthDataLoginOption();
option.AsMainAccount = false;
option.UnionIdPlatform = "weixin"; // 这里指定 unionIdPlatform,使用「weixin」来指代微信平台。
-TDSUser currentUser = await TDSUser.LoginWithAuthDataAndUnionId(
+LCUser currentUser = await LCUser.LoginWithAuthDataAndUnionId(
thirdPartyData, "wxleansupport", "unionid4a",
option: option);
```
@@ -994,14 +3351,14 @@ thirdPartyData.put("expires_in", 1384686496);
thirdPartyData.put("uid", "supportopenid");
thirdPartyData.put("access_token", "supporttoken");
thirdPartyData.put("scope", "SCOPE");
-TDSUser.loginWithAuthData(TDSUser.class, thirdPartyData, "wxleansupport", "unionid4a",
+LCUser.loginWithAuthData(LCUser.class, thirdPartyData, "wxleansupport", "unionid4a",
"weixin", // 这里指定 unionIdPlatform,使用「weixin」来指代微信平台。
- false).subscribe(new Observer() {
+ false).subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
- public void onNext(TDSUser user) {
+ public void onNext(LCUser user) {
System.out.println("登录成功");
}
@Override
@@ -1022,7 +3379,7 @@ NSDictionary *thirdPartyData = @{
@"scope":@"SCOPE",
@"unionid":@"unionid4a"
};
-TDSUser *currentuser = [TDSUser user];
+LCUser *currentuser = [LCUser user];
LCUserAuthDataLoginOption *option = [LCUserAuthDataLoginOption new];
option.platform = @"weixin"; // 这里指定 unionIdPlatform,使用「weixin」来指代微信平台。
option.unionId = thirdPartyData[@"unionid"];
@@ -1036,6 +3393,89 @@ option.isMainAccount = false;
}];
```
+```swift
+let thirdPartyData: [String: Any] = [
+ "access_token": "supporttoken",
+ "expires_in": 1384686496,
+ "uid": "supportopenid",
+ "scope": "SCOPE",
+ "unionid": "unionid4a"
+]
+let user = LCUser()
+user.logIn(
+ authData: thirdPartyData,
+ platform: .custom("wxleansupport"),
+ unionID: thirdPartyData["unionid"] as? String,
+ unionIDPlatform: .weixin, // 这里指定 unionIdPlatform,使用「weixin」来指代微信平台。
+ options: [.mainAccount])
+{ (result) in
+ switch result {
+ case .success:
+ assert(user.objectId != nil)
+ case .failure(error: let error):
+ print(error)
+ }
+}
+```
+
+```dart
+Map thirdPartyData = {
+ // 必须
+ 'uid': 'supportopenid',
+ 'access_token': 'supporttoken',
+ 'expires_in': 1384686496,
+ 'unionId': 'unionid4a',
+
+ // 可选
+ 'refresh_token': '...',
+ 'scope': 'SCOPE'
+};
+LCUserAuthDataLoginOption option = LCUserAuthDataLoginOption();
+option.asMainAccount = false;
+option.unionIdPlatform = 'weixin'; // 这里指定 unionIdPlatform,使用「weixin」来指代微信平台。
+LCUser currentUser = await LCUser.loginWithAuthDataAndUnionId(
+ thirdPartyData, 'wxleansupport', 'unionid4a',
+ option: option);
+```
+
+```js
+const thirdPartyData = {
+ access_token: "supporttoken",
+ expires_in: 1384686496,
+ uid: "supportopenid",
+ scope: "SCOPE",
+};
+
+AV.User.loginWithAuthDataAndUnionId(
+ thirdPartyData,
+ "wxleansupport",
+ "unionid4a",
+ {
+ unionIdPlatform: "weixin", // 这里指定 unionIdPlatform,使用「weixin」来指代微信平台。
+ asMainAccount: false,
+ }
+).then(
+ (user) => {
+ // 绑定成功
+ },
+ (error) => {
+ // 绑定失败
+ }
+);
+```
+
+```python
+# 暂不支持
+```
+
+```php
+// 暂不支持
+```
+
+```go
+// 暂不支持
+```
+
与「云服务通讯」中的登录过程相比,在「云服务技术支持」这个应用中,我们在登录时只是将 `asMainAccount` 设为了 `false`。 这时我们看到,本次登录得到的还是 `objectId` 为 `ThisIsUserA` 的 `_User` 表记录(同一个账户),同时该账户的 `authData` 属性中发生了变化,多了 `wxleansupport` 的数据,如下:
@@ -1064,11 +3504,11 @@ option.isMainAccount = false;
}
```
-在新的登录方式中,当一个用户以「平台名为 `wxleanoffice`、uid 为 `officeopenid`、UnionID 为 `unionid4a`」的第三方鉴权信息登录得到新的 `TDSUser` 后,接下来这个用户以「平台名为 `wxleansupport`、uid 为 `supportopenid`、UnionID 为 `unionid4a`」的第三方鉴权信息登录时,云端判定是同样的 UnionID,就直接把来自 `wxleansupport` 的新用户数据加入到已有账户的 `authData` 里了,不会再创建新的账户。
+在新的登录方式中,当一个用户以「平台名为 `wxleanoffice`、uid 为 `officeopenid`、UnionID 为 `unionid4a`」的第三方鉴权信息登录得到新的用户对象后,接下来这个用户以「平台名为 `wxleansupport`、uid 为 `supportopenid`、UnionID 为 `unionid4a`」的第三方鉴权信息登录时,云端判定是同样的 UnionID,就直接把来自 `wxleansupport` 的新用户数据加入到已有账户的 `authData` 里了,不会再创建新的账户。
-这样一来,云端通过识别平台性的用户唯一标识 UnionID,让来自同一个 UnionID 体系内的应用程序、小程序等不同平台的用户都绑定到了一个 `TDSUser` 上,实现互通。
+这样一来,云端通过识别平台性的用户唯一标识 UnionID,让来自同一个 UnionID 体系内的应用程序、小程序等不同平台的用户都绑定到了一个用户对象上,实现互通。
-#### 为 UnionID 建立索引
+### 为 UnionID 建立索引
云端会为 UnionID 自动建立索引,不过因为自动创建基于请求的抽样统计,可能会滞后。
因此,我们推荐自行创建相关索引,特别是用户量(`_User` 表记录数)很大的应用,更需要预先创建索引,否则用户使用 UnionID 账号登录时可能超时失败。
@@ -1078,7 +3518,7 @@ option.isMainAccount = false;
- `authData.wxleansupport.uid`
- `authData._weixin_unionid.uid`
-#### 该如何指定 unionIdPlatform
+### 该如何指定 unionIdPlatform
从上面的例子可以看出,使用 UnionID 登录的时候,需要指定 `unionIdPlatform` 的主要目的,就是为了便于查找已经存在的唯一主账号。云端会在主账号对应账户的 `authData` 属性中增加一个 `_{unionIdPlatform}_unionid` 键值对来标记唯一性,终端用户在其他应用中登录的时候,云端会根据参数中提供的 `uniondId` + `unionIdPlatform` 的组合,在 `_User` 表中进行查找,这样来确定唯一的既存主账号。
@@ -1094,11 +3534,11 @@ option.isMainAccount = false;
- 微博平台的多个应用,统一使用 `weibo` 作为 `unionIdPlatform`;
- 除此之外的其他平台,开发者可以自行定义 `unionIdPlatform` 的名字,只要自己保证多个应用间统一即可。
-#### 主副应用不同登录顺序出现的不同结果
+### 主副应用不同登录顺序出现的不同结果
上面的流程是用户先登录了「云服务通讯」这个主应用,然后再登录「云服务技术支持」这个副应用,所以账号都被通过 UnionID 有效关联起来了。可能有人会想到另外一个问题,如果用户 B 先登录副应用,后登录主应用,这时候会发生什么事情呢?
-用户 B 首先登录副应用的时候,提供了「平台名为 `wxleansupport`、uid 为 `supportopenid`、UnionID 为 `unionid4a`」的第三方鉴权信息,并且指定「UnionIDPlatform 为 `weixin`、`asMainAccount` 为 `false`」(与上面的调用完全一致),此时云端由于找不到存在的 UnionID,会新建一个 `TDSUser` 对象,该账户 `authData` 结果为:
+用户 B 首先登录副应用的时候,提供了「平台名为 `wxleansupport`、uid 为 `supportopenid`、UnionID 为 `unionid4a`」的第三方鉴权信息,并且指定「UnionIDPlatform 为 `weixin`、`asMainAccount` 为 `false`」(与上面的调用完全一致),此时云端由于找不到存在的 UnionID,会新建一个用户对象,该账户 `authData` 结果为:
```json
{
@@ -1113,7 +3553,7 @@ option.isMainAccount = false;
}
```
-用户 B 接着又使用了主应用,ta 再次通过微信登录,此时以「平台名为 `wxleanoffice`、uid 为 `officeopenid`、UnionID 为 `unionid4a`」的第三方鉴权信息,以及「UnionIDPlatform 为 `weixin`、`asMainAccount` 为 `true`」的参数进行登录,此时云端由于找不到存在的 UnionID,会再次新建一个 `TDSUser` 对象,该账户 `authData` 结果为:
+用户 B 接着又使用了主应用,ta 再次通过微信登录,此时以「平台名为 `wxleanoffice`、uid 为 `officeopenid`、UnionID 为 `unionid4a`」的第三方鉴权信息,以及「UnionIDPlatform 为 `weixin`、`asMainAccount` 为 `true`」的参数进行登录,此时云端由于找不到存在的 UnionID,会再次新建一个用户对象,该账户 `authData` 结果为:
```json
{
@@ -1133,7 +3573,7 @@ option.isMainAccount = false;
还有更复杂的情况。如果某公司的产品之前就接入了微信登录,产生了很多存量用户,并且分散在不同的子产品中,这时候怎么办?我们接下来专门讨论此时的解决方案。
-#### 存量账户如何通过 UnionID 实现关联
+### 存量账户如何通过 UnionID 实现关联
还是以我们的两个子产品「云服务通讯」(后续以「产品 1」指代)和「云服务技术支持为例」(后续以「产品 2」指代)为例,在接入 UnionID 之前,我们就接入了之前版本的微信平台登录,这时候账户系统内可能存在多种账户:
@@ -1192,4 +3632,221 @@ option.isMainAccount = false;
-
+## 匿名用户
+
+将数据与用户关联需要首先创建一个用户,但有时你不希望强制用户在一开始就进行注册。使用匿名用户,可以让应用不提供注册步骤也能创建用户。下面的代码创建一个新的匿名用户:
+
+
+
+```cs
+await LCUser.LoginAnonymously();
+```
+
+```java
+LCUser.logInAnonymously().subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCUser user) {
+ // user 是新的匿名用户
+ }
+ public void onError(Throwable throwable) {}
+ public void onComplete() {}
+});
+```
+
+```objc
+[LCUser loginAnonymouslyWithCallback:^(LCUser *user, NSError *error) {
+ // user 是新的匿名用户
+}];
+```
+
+```swift
+// 暂不支持
+```
+
+```dart
+await LCUser.loginAnonymously();
+```
+
+```js
+AV.User.loginAnonymously().then((user) => {
+ // user 是新的匿名用户
+});
+```
+
+```python
+# 暂不支持
+```
+
+```php
+// 暂不支持
+```
+
+```go
+// 暂不支持
+```
+
+
+
+可以像给普通用户设置属性那样给匿名用户设置 `username`、`password`、`email` 等属性,还可以通过走正常的注册流程来将匿名用户转化为普通用户。匿名用户能够:
+
+- [使用用户名和密码注册](#注册)
+- [关联第三方平台](#第三方账户登录),比如微信
+
+下面的代码为一名匿名用户设置用户名和密码:
+
+
+
+```cs
+LCUser currentUser = await LCUser.LoginAnonymously();
+currentUser.Username = "Tom";
+currentUser.Password = "cat!@#123";
+
+await currentUser.SignUp();
+```
+
+```java
+// currentUser 是个匿名用户
+LCUser currentUser = LCUser.getCurrentUser();
+
+currentUser.setUsername("Tom");
+currentUser.setPassword("cat!@#123");
+
+currentUser.signUpInBackground().subscribe(new Observer() {
+ public void onSubscribe(Disposable disposable) {}
+ public void onNext(LCUser user) {
+ // currentUser 已经转化为普通用户
+ }
+ public void onError(Throwable throwable) {
+ // 注册失败(通常是因为用户名已被使用)
+ }
+ public void onComplete() {}
+});
+```
+
+```objc
+// currentUser 是个匿名用户
+LCUser *currentUser = [LCUser currentUser];
+
+user.username = @"Tom";
+user.password = @"cat!@#123";
+
+[user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
+ if (succeeded) {
+ // currentUser 已经转化为普通用户
+ } else {
+ // 注册失败(通常是因为用户名已被使用)
+ }
+}];
+```
+
+```swift
+// 暂不支持
+```
+
+```dart
+LCUser currentUser = await LCUser.loginAnonymously();
+currentUser.username = 'Tom';
+currentUser.password = 'cat!@#123';
+
+await currentUser.signUp();
+```
+
+```js
+// currentUser 是个匿名用户
+const currentUser = AV.User.current();
+
+user.setUsername("Tom");
+user.setPassword("cat!@#123");
+
+user.signUp().then(
+ (user) => {
+ // currentUser 已经转化为普通用户
+ },
+ (error) => {
+ // 注册失败(通常是因为用户名已被使用)
+ }
+);
+```
+
+```python
+# 暂不支持
+```
+
+```php
+// 暂不支持
+```
+
+```go
+// 暂不支持
+```
+
+
+
+下面的代码检查当前用户是否为匿名用户:
+
+
+
+```cs
+LCUser currentUser = await LCUser.GetCurrent();
+if (currentUser.IsAnonymous) {
+ // currentUser 是匿名用户
+} else {
+ // currentUser 不是匿名用户
+}
+```
+
+```java
+LCUser currentUser = LCUser.getCurrentUser();
+if (currentUser.isAnonymous()) {
+ // currentUser 是匿名用户
+} else {
+ // currentUser 不是匿名用户
+}
+```
+
+```objc
+LCUser *currentUser = [LCUser currentUser];
+if (currentUser.isAnonymous) {
+ // currentUser 是匿名用户
+} else {
+ // currentUser 不是匿名用户
+}
+```
+
+```swift
+// 暂不支持
+```
+
+```dart
+LCUser currentUser = await LCUser.getCurrent();
+if (currentUser.isAnonymous) {
+ // currentUser 是匿名用户
+} else {
+ // currentUser 不是匿名用户
+}
+```
+
+```js
+const currentUser = AV.User.current();
+if (currentUser.isAnonymous()) {
+ // currentUser 是匿名用户
+} else {
+ // currentUser 不是匿名用户
+}
+```
+
+```python
+# 暂不支持
+```
+
+```php
+// 暂不支持
+```
+
+```go
+// 暂不支持
+```
+
+
+
+如果匿名用户未能在登出前转化为普通用户,那么该用户将无法再次登录同一账户,且之前产生的数据也无法被取回。
diff --git a/leancloud/docs/sdk/authentication/rest.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/authentication/rest.mdx
similarity index 100%
rename from leancloud/docs/sdk/authentication/rest.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/authentication/rest.mdx
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/copyright-verification/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/copyright-verification/_category_.json
deleted file mode 100644
index 57b7c1615..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/copyright-verification/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "付费下载和正版验证",
- "collapsed": true,
- "position": 11
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/copyright-verification/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/copyright-verification/features.mdx
deleted file mode 100644
index ee1ae632f..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/copyright-verification/features.mdx
+++ /dev/null
@@ -1,66 +0,0 @@
----
-title: Paid Games & Copyright Verification
-sidebar_label: Features
-sidebar_position: 1
----
-
-import useBaseUrl from "@docusaurus/useBaseUrl";
-import { Conditional } from "/src/docComponents/conditional";
-
-## Overview
-
-TapTap offers copyright verification services for pay-to-download games. When a player launches a paid game, the service can verify if the player has purchased the game.
-
-## How to Enable
-
-You will need to submit an application to enable this service. Please follow the instructions on the following screenshot.
-
-
-
-
-
-
-
-
-## Price Settings
-
-TapTap’s copyright verification service must be configured together with the paid game’s price settings. TapTap helps you manage your paid games and offers multiple payment methods for the players. You can make your game a paid game in the Price Settings at [TapTap Developer Center](https://developer.taptap.cn/)[TapTap Developer Center](https://developer.taptap.io/) and set up the price. If you want to start a promotion for your game, you can turn on the promotion feature and specify the promotion period and discounted price for your game.
-
-
-
-
-
-
-
-
-## Integrating SDK
-
-After you integrate the copyright verification SDK into your game, the SDK will make a verification query on purchase results when a user launches the game. The user will be able to access the game if it has been purchased normally. Otherwise, a message will remind the player to purchase the game first. Refer to [Developer Guide](/sdk/copyright-verification/guide/) for details about the integration.
-
-## FAQ
-
-### Do I have to integrate the Copyright Verification SDK if I’m to release a paid game on TapTap?
-
-It’s not a requirement for the games released on TapTap to integrate the Copyright Verification SDK.
-But without the SDK, you can only expect TapTap to stop the unpaid players from downloading your game from TapTap.
-
-- If a player downloads the APK of the game from somewhere else, they can still install and enter the game.
-- If a player purchased the game from TapTap and later on requested a refund, the player won’t be able to download the game anymore, but they can still enter the game they already installed on their device.
-
-That’s why we highly recommend you to integrate the Copyright Verification SDK in your game, as it is the easiest way for you to prevent players from playing unauthorized copies of your game.
-If you choose not to use the SDK, you will have to implement the anti-piracy mechanism yourself.
-
-At this moment, if you haven’t enabled Copyright Verification on the Developer Center, you won’t be able to access the Price Settings.
-This means that even you only need to launch a paid game without using the Copyright Verification SDK in your game, you still need to enable the Copyright Verification feature on the Developer Center.
-Once your application to enable the feature has been approved, you will be able to access the Price Settings.
-
-## How much share does TapTap take?
-
-TapTap does not take a share of the revenue.
-
-However, there will be a 5% payment processing fee in the Mainland China region. For other countries and regions, the fee will be determined upon communication (the fee includes third-party payment channel processing fees and tax costs).
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/copyright-verification/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/copyright-verification/guide.mdx
deleted file mode 100644
index b70e6277b..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/copyright-verification/guide.mdx
+++ /dev/null
@@ -1,208 +0,0 @@
----
-title: Copyright Verification
-sidebar_label: Guide
-sidebar_position: 2
----
-
-import { Conditional } from "/src/docComponents/conditional";
-import MultiLang from "/src/docComponents/MultiLang";
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-import AndroidFaq from "../_partials/android-package-visibility.mdx";
-
-If you ever thought about selling your game online, you probably have worried if the players could bypass the legitimate process of paying for your game and download copies of your game from unauthorized sources like piracy websites. Luckily, TapTap offers an easy-to-use Copyright Verification SDK that lets you perform a quick license check when a player opens your game for the first time. If the player opens your game without purchasing it beforehand, the SDK will guide the player to make a purchase. This ensures that a player who didn’t purchase your game won’t be able to enter the game even if they managed to obtain a copy of the game.
-
-## Installing SDK
-
-You can download the TapSDK from the [Downloads](/tap-download) page. Once you have the SDK on your computer, add them to your project:
-
-
-
-<>
-
-You can add the SDK **either manually or with the Unity Package Manager**.
-
-If you choose to use the Unity Package Manager, you should add the following dependencies into `Packages/manifest.json`:
-
-
- {`"dependencies":{
- "com.taptap.tds.common":"https://github.com/TapTap/TapCommon-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.dlc": "https://github.com/TapTap/TapLicense-Unity.git#${sdkVersions.taptap.unity}",
- "com.leancloud.storage": "https://github.com/leancloud/csharp-sdk-upm.git#storage-${sdkVersions.leancloud.csharp}",
-}`}
-
-
-If you choose to manually import the SDK, you should:
-
-* In the [download page](/tap-download), click `TapSDK Unity` to download `TapSDK-UnityPackage.zip`.
-* Go to your Unity project, navigate to **Assets > Import Package > Custom Package**, select the `TapTap_Common` and `TapTap_License` modules from unzipped SDK.
-* Download [LeanCloud-SDK-Storage-Unity.zip](https://github.com/leancloud/csharp-sdk/releases), unzip it as a `Plugins` folder, and drag and drop the folder into Unity.
-
->
-
-<>
-
-Put the SDK into `project/app/libs` and then add the following lines into `project/app/build.gradle`:
-
-
- {`repositories{
- flatDir {
- dirs 'libs'
- }
-}
-dependencies {
- implementation name:'TapCommon_${sdkVersions.taptap.android}', ext:'aar'
- implementation name:'TapLicense_${sdkVersions.taptap.android}', ext:'aar'
-}`}
-
-
->
-
-<>
-
-```objc
-// Not supported yet
-```
-
->
-
-<>
-
-Copy the ** TapLicense ** , ** TapCommon ** plugins into the project's plugins directory and add the dependencies to the ** build.cs ** file of the project module:
-
-```csharp
-PublicDependencyModuleNames.AddRange(new string[] {
- "Json",
- "JsonUtilities",
- "SlateCore",
- "TapCommon",
- "TapLicense",
-});
-```
-
->
-
-
-
-## Set Up Authorization Callback
-
-
-
-```cs
-// The `License` library (required)
-using TapTap.License;
-
-// By default, the SDK will display a window
-// that can’t be closed manually by the player to avoid unauthorized players from entering the game.
-// If you want to use a callback to trigger a customized procedure,
-// please add the following code.
-TapLicense.SetLicencesCallback(ITapLicenseCallback callback);
-
-public interface ITapLicenseCallback
-{
- // Authorization success callback
- void OnLicenseSuccess();
-}
-```
-
-```java
-// By default, the SDK will display a window
-// that can’t be closed manually by the player to avoid unauthorized players from entering the game.
-// If you want to use a callback to trigger a customized procedure,
-// please add the following code.
-TapLicenseHelper.setLicenseCallback(new TapLicenseCallback() {
- @Override
- public void onLicenseSuccess() {
- // Authorization success callback
- }
-});
-```
-
-```objc
-// Not supported yet
-```
-
-```cpp
-// The `License` library is required.
-#include "TapLicense.h"
-
-// By default, the SDK displays a popup that cannot be manually cancelled by the player to prevent unauthorised players from entering the game; if you need a callback to trigger the process, add the following code
-FTapLicense::SetLicenseCallback(FSimpleDelegate::CreateLambda([]() {
- // Authorisation successful
-}));
-```
-
-
-
-## Payment and Authorization Verification
-
-The default value of the parameter in the Check method is false, which means that the SDK will confirm whether or not the currently logged in user has purchased the game via the TapTap client the first time and again after the 5th day from the first trigger. If you use the value of true, then every time the interface is called it will confirm whether or not the currently logged in user has purchased the game via the TapTap client.
-
-
-
-```cs
-TapLicense.Check();
-TapLicense.Check(true)
-```
-
-```java
-TapLicenseHelper.check(Activity activity);
-TapLicenseHelper.check(Activity activity, boolean forceCheck);
-```
-
-```objc
-// Not supported yet
-```
-
-```cpp
-FTapLicense::Check();
-FTapLicense::Check(true);
-```
-
-
-
-## Compatibility with Android 11 and later versions
-
-
-
-## Testing
-
-To ensure that the game can determine whether a player has a valid purchase after it has been released, **please follow the instructions below to complete a self-testing.**
-
-### 1. Upload the APK
-
-Open your game in the Developer Center and go to **Game Services > Gaming Ecosystem > Copyright Verification > Package Name**.
-
-Here you can upload the APK to be tested. Once you upload the APK, please wait for the review process to be completed.
-
-### 2. Add Test Accounts
-
-Go to **Developer Center > Game Services > Gaming Ecosystem > Copyright Verification > Game Configuration > Manage testers** and enter the test accounts’ TapTap IDs.
-
-### 3. Price Settings
-
-You can set a price for your game by going to **Developer Center > Store > Price Settings**. For testing purposes, you can set the price to ¥0.01 CNY $1.00 USD. Once you set the price, please wait for the review process to be completed.
-
-### 4. Begin the Test
-
-Now you can open the TapTap app on your device and log in with a test account. Start the testing process from the store page of the app.
-
-You can make a purchase of the game with the test account and then download and enter the game.
-
-If you install the game with the APK without having the test account purchase the game first, there will be a pop-up when you open the game. The pop-up will say that the game is not activated and you should purchase the game from TapTap.
-
-## Start Selling Your Game
-
-Once you finish the testing process, you’ll be ready to sell your game.
-
-### 1. Complete Information
-
-Go to the Developer Center, fill in the information, and submit your game for review.
-
-### 2. Set Up Pricing
-
-Go to **Developer Center > Store > Price Settings**, make your game a paid game, set a price for your game, and submit the information for review. Don’t forget to update the TapTap operation staff on your progress.
-
-### 3. Official Release
-
-After completing the steps above, your game will be ready for the official release.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/dlc/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/dlc/_category_.json
deleted file mode 100644
index 0e4f3783b..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/dlc/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "DLC",
- "collapsed": true,
- "position": 12
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/dlc/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/dlc/features.mdx
deleted file mode 100644
index 1a624181d..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/dlc/features.mdx
+++ /dev/null
@@ -1,34 +0,0 @@
----
-title: DLC Introduction
-sidebar_label: Introduction
-sidebar_position: 1
----
-
-import useBaseUrl from "@docusaurus/useBaseUrl";
-import { Conditional } from "/src/docComponents/conditional";
-
-Many games offer downloadable content (DLC) that players can purchase to unlock. With TapTap Developer Services, you can easily implement the same feature in your game. The players can browse and purchase the products you set up in your game without leaving the game. You can even set up bundles that contain a collection of products. Once a player triggers the purchase operation in your game, the DLC SDK will handle the rest of the work to guide the player to make the purchase.
-
-Usually, a player will immediately receive the product once they make a purchase. If the player fails to make a purchase or chooses to cancel the purchase, your game will be notified by the SDK. Please follow the development guide to integrate the SDK into your game.
-
-## Enabling the Feature
-
-
-
-Please reach out to us if you wish to enable DLC in your game.
-
-
-
-
-
-Please send an email to [international_operation@taptap.com](mailto:international_operation@taptap.com) if you wish to enable DLC in the game.
-
-
-
-## Updating DLC
-
-To add more products available as DLC in your game, please add them to your game’s bundle and upload an updated version of the APK to TapTap. Once the player updates the game, they will see the added products in the game.
-
-## Selling and Refunding
-
-Your DLC will be available for players to purchase once you release your game. To provide the best experience to the players, TapTap allows players to request refunds on what they have purchased. You can view detailed sales data on [TapTap Developer Center](https://developer.taptap.cn/)[TapTap Developer Center](https://developer.taptap.io/).
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/dlc/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/dlc/guide.mdx
deleted file mode 100644
index 7c5b00080..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/dlc/guide.mdx
+++ /dev/null
@@ -1,189 +0,0 @@
----
-title: DLC Integration Guide
-sidebar_label: Guide
-sidebar_position: 2
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-import { Gray, Blue, Red, Black } from "/src/docComponents/doc";
-import { Conditional } from "/src/docComponents/conditional";
-
-## Querying and Purchasing DLC
-
-Please [download](/tap-download) the TapSDK and add the following dependencies to your game:
-
-
-
-<>
-
-You can add the SDK **either manually or with the Unity Package Manager**.
-
-If you choose to use the Unity Package Manager, you should add the following dependencies into `Packages/manifest.json`:
-
-
- {`"dependencies":{
- "com.taptap.tds.common":"https://github.com/TapTap/TapCommon-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.dlc": "https://github.com/TapTap/TapLicense-Unity.git#${sdkVersions.taptap.unity}",
- "com.leancloud.storage": "https://github.com/leancloud/csharp-sdk-upm.git#storage-${sdkVersions.leancloud.csharp}",
-}`}
-
-
-If you choose to manually import the SDK, you should:
-
-* In the [download page](/tap-download), click `TapSDK Unity` to download `TapSDK-UnityPackage.zip`.
-* Go to your Unity project, navigate to **Assets > Import Package > Custom Package**, select the `TapTap_Common` and `TapTap_License` modules from unzipped SDK.
-* Download [LeanCloud-SDK-Storage-Unity.zip](https://github.com/leancloud/csharp-sdk/releases), unzip it as a `Plugins` folder, and drag and drop the folder into Unity.
-
->
-
-<>
-
-Import the SDK to the `project/app/libs` of your game project. Then open `project/app/build.gradle` and add the following lines:
-
-
- {`repositories{
- flatDir {
- dirs 'libs'
- }
-}
-dependencies {
- implementation name:'TapCommon_${sdkVersions.taptap.android}', ext:'aar'
- implementation name:'TapLicense_${sdkVersions.taptap.android}', ext:'aar'
-}`}
-
-
->
-
-<>
-
-```objc
-// Not supported yet
-```
-
->
-
-
-
-### DLC Callback Settings
-
-
-
-```cs
-public class MyTapDLCCallback:ITapDlcCallback
-{
- public void OnQueryCallBack(TapLicenseQueryCode code, Dictionary queryList)
- {
-
- }
-
- public void OnOrderCallBack(string sku, TapLicensePurchasedCode status)
- {
-
- }
-}
-
-TapLicense.SetDLCCallback(new MyTapDLCCallback());
-```
-
-```java
-TapLicenseHelper.setDLCCallback(new DLCManager.InventoryCallback() {
- @Override
- public boolean onQueryCallBack(int i, HashMap hashMap) {
- return false;
- }
-
- @Override
- public void onOrderCallBack(String s, int i) {
-
- }
-});
-```
-
-```objc
-// Not supported yet
-```
-
-
-
-### Querying DLC
-
-
-
-```cs
-TapLicense.QueryDLC(string[] skuIds);
-```
-
-```java
-TapLicenseHelper.queryDLC(Activity activity, String[] skuIds);
-```
-
-```objc
-// Not supported yet
-```
-
-
-
-### Purchasing DLC
-
-
-
-```cs
-TapLicense.PurchaseDLC(string skuId);
-```
-
-```java
-TapLicenseHelper.purchaseDLC(Activity activity, String skuIds);
-```
-
-```objc
-// Not supported yet
-```
-
-
-
-### Parameters
-
-#### TapLicenseQueryCode
-
-| Callback | Callback value | Description |
-| ------------------------------- | -------------- | ------------------------------------- |
-| QUERY_RESULT_OK | 0 | Query succeeded |
-| QUERY_RESULT_NOT_INSTALL_TAPTAP | 1 | TapTap is not installed on the device |
-| QUERY_RESULT_ERR | 2 | Query failed |
-| ERROR_CODE_UNDEFINED | 80000 | Unknown error |
-
-#### skuId
-
-This is a unique ID assigned to each product. Please reach out to TapTap to have them set up.
-
-## Testing
-
-To ensure that players are able to make purchases once you have released your game, **please follow the instructions below to test the payment procedure**
-
-### Upload APK
-
-Upload the APK of your game to the TapTap Developer Center and wait for it to go through the review process.
-
-### Add Test Users
-
-Go to TapTap Developer Center >> Click on Game Services >> Click on Game Ecosystem >> Click onCopyright Verification >> Enter the TapTap ID of the test user
-
-### Start Testing
-
-Log in to the TapTap app on your device with the test account.
-
-## Start Selling
-
-### Complete Application Information
-
-Go to TapTap Developer Center and fill in the application information, then wait for the review.
-
-### Set Prices
-
-Go to TapTap Developer Center >> Price Settings, enable the feature, set up the prices, and submit everything for review.
-
-### Release Your Game
-
-If everything worked out well, you may release your game.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/embedded-moments/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/embedded-moments/_category_.json
deleted file mode 100644
index 62bdd9b00..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/embedded-moments/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "内嵌动态",
- "collapsed": true,
- "position": 4
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/embedded-moments/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/embedded-moments/features.mdx
deleted file mode 100644
index 0b5171c2d..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/embedded-moments/features.mdx
+++ /dev/null
@@ -1,199 +0,0 @@
----
-title: Embedded Moments Features
-sidebar_label: Features
-sidebar_position: 1
----
-
-## Introduction
-
-By adding Embedded Moments to your game, you can have players access TapTap’s forum without leaving the game so they can easily browse walkthroughs, share their game highlights, and interact with other players and officials.
-
-## Core Advantages
-
-**For game developers:**
-
-- Allow players to share their gameplay with just a single click.
-- Display officially published content on players’ feeds.
-- View what players have posted and provide timely feedback to them.
-
-**For game players:**
-
-- Communicate and interact with other players on a delayed basis while gaming.
-- Look up walkthroughs and top players’ solutions when stuck on certain scenes in the game.
-
-## Account System
-
-For a player to make a post or interact with other posts using Embedded Moments, they have to log in to a TapTap account. Therefore, you need to enable **[TapTap Login](/sdk/taptap-login/features/)** for your game before you can use Embedded Moments.
-
-![](https://capacity-files.lcfile.com/ILQvpPvMr6YfmdpSyMORt8ylRoNFjl75/taplogin-moment.png)
-
-## Moments
-
-### Game Forum
-
-A player can access the TapTap forum directly from the “Games” module:
-
-![](https://capacity-files.lcfile.com/9Kbu67cyRsrEXOk6H4flyBTVDV37lWtw/game.png)
-
-The following modules are included in the “Games” module:
-
-![](https://capacity-files.lcfile.com/KIBDwA9wha829rk2uH9ewWrnK4tWIdw8/game-detail.png)
-
-1. **Sub-groups**: Sub-groups can help users filter feeds by categories. The settings of sub-groups are shared between the TapTap forum and Embedded Moments. You can edit sub-groups in “Group Management Center > Sub-group” and the edits will be displayed to the players once they are approved.
-
-2. **Banners**: Banners can help you convey important notifications and events to players. This is a module exclusive to Embedded Moments. You can edit banners in “Gaming Ecosystem > Embedded Moments > Banner Configuration” and the edits will be displayed to the players once they are approved.
-
-3. **Recommendations**: The recommendations section can be used to hold shortcuts to posts, sub-groups, and index pages. The settings of recommendations are also shared between the TapTap forum and Embedded Moments. You can edit recommendations in “Group Management Center > Recommendation” and the edits will be displayed to the players once they are approved.
-
-4. **Feeds**: Trending posts are displayed to the player when they first enter Embedded Moments. The player can choose to sort posts by the time of the last reply or the time the post is published.
-
-The following functions are also available to you:
-
-- **Posting moments**: Players can post moments containing pictures and videos to the forum.
-
-![](https://capacity-files.lcfile.com/0xlsu9vXy0Hkwel36Snj1MRsf7sAvtDf/post.png)
-
-- **Interactions**: Players can **like, comment, and repost** other players’ moments.
-
-![](https://capacity-files.lcfile.com/OPvQNI7N8RFErcWlXJksJLTzRiCwBmju/repost.png)
-
-### Walkthroughs
-
-#### Walkthrough Labels
-
-The player can look for “labels” in the quick navigation bar on the right with each containing three pieces of walkthroughs:
-
-![](https://capacity-files.lcfile.com/Nzt5T3XotWMLSFBuJaBB9kXrPR4hsGqw/tips.png)
-
-The player can click on the “View all” button on the top to view all the walkthroughs under the current “label”:
-
-![](https://capacity-files.lcfile.com/J6wisBtATvR3H36kUGQyq5zc0Wp1RTJD/all-tips.png)
-
-#### Walkthrough Indexes
-
-You can set up indexes that are displayed under “All walkthroughs” according to the number and types of walkthroughs.
-
-![](https://capacity-files.lcfile.com/6dYy2HATlXhgT0O1IEuuTV7Pq1ObOVmI/all.png)
-
-#### Notes on Walkthroughs
-
-##### Why can’t I see the “Walkthroughs” module in my game’s Embedded Moments? How do I enable it?
-
-If you want to add a “Walkthroughs” module, please reach out to us by submitting a **ticket** in the TapTap Developer Center.
-
-##### Can I set up walkthroughs in the TapTap Developer Center? How should I do it?
-
-Unfortunately, you can’t set up walkthroughs yourself at this time. Please submit a **ticket** to us and we will have our professional editor team help you figure things out.
-
-##### Submitting Tickets
-
-To submit a ticket, go to the TapTap Developer Center and click on “Tickets” at the top-right corner.
-
-### Feeds
-
-Players who logged in to TapTap can view the moments posted by their friends and the official accounts. When there are new moments available, there will be a **badge** on the “Follow” section on the navigation bar. This ensures that players will never miss out on important updates.
-
-![](https://capacity-files.lcfile.com/6dYy2HATlXhgT0O1IEuuTV7Pq1ObOVmI/all.png)
-
-### Profile Page
-
-Players can find their past moments on the “Me” page. Here players can share their moments on other apps or delete their moments.
-
-![](https://capacity-files.lcfile.com/VXrB4UDQrhEILMd9CPyPNtWvVyreuLOf/me.png)
-
-Players can view notifications by tapping the “Alarm” icon on the top right corner. Interactions between players will trigger notifications, which encourages players to interact more with each other.
-
-![](https://capacity-files.lcfile.com/6HHynVGKc7nD0HmPAYM9RIP9SLgAUuPD/msg.png)
-
-## SDK Features
-
-### Scenario-Based Portals
-
-You can make any of the objects in your game as a portal that opens up Embedded Moments. You can even specify landing pages for certain scenes in the [Developer Center](#scenario-based-portal-configuration). This could be helpful if you want to allow players to quickly get help from the community when they’re stuck on certain scenes in the game.
-
-:::tip
-
-1. TDS doesn’t provide any guidelines for the design of portals. We encourage you to design your portals so they look harmonious with the scenes they’re placed at.
-2. The landing page of a portal can be set up to be an article or a specific module according to your own needs.
-
-:::
-
-![](https://capacity-files.lcfile.com/8DLQ5ISYjGdB4Y8rXNPkvV2jnitUAzfe/scenario-portal.png)
-
-### Badges
-
-You can place buttons that can display badges in your game so that the players can be attracted to open the Embedded Moments when they see the badges.
-
-![](https://capacity-files.lcfile.com/D8CIJaldFjoS6fHkFmv92XqVClt7XYu0/red-dot.png)
-
-:::tip
-
-1. Using badges can help you increase the chance for players to open the Embedded Moments. We encourage you to place buttons with badges on prominent places in your game.
-2. The badges here share the same logic as the badges for the “Follow” module within the Embedded Moments. The new content posted by the users followed by the players will trigger a notification, and the interval of retrieving new notifications is once per minute (here 1 minute is the minimum interval; you can change it to 3 minutes, 5 minutes, etc.).
-3. Once the player opens the Embedded Moments, the game needs to clear the badge and continue inquiring for the next display of the badge.
-
-:::
-
-### Quick Sharing
-
-Players can take screenshots within the game and quickly share them on Embedded Moments. Only text and images can be shared through this method.
-
-![](https://capacity-files.lcfile.com/QUWwDR2u9lJxXiR2xvPNNhKpYf1iffRR/share.png)
-
-### Pop-up for Dynamically Closing Embedded Moments
-
-While the player is browsing Embedded Moments, if there are events that demand the player to immediately return to the game, a pop-up can be displayed to serve as a reminder and offer a shortcut for the player to close the Embedded Moments.
-
-![](https://capacity-files.lcfile.com/jDRpSrhLkTavEveFtbqgu183AVoDjHXp/popup.png)
-
-## Administration
-
-### Theme Configuration
-
-To have Embedded Moments fit better with the game scenes and not make players feel cut off, TDS allows you to customize the theme of the Embedded Moments. You can upload a background image and specify the colors of texts in “Game Services” > “Embedded Moments” > “Theme”.
-
-:::tip
-
-1. You can use [Embedded Moments Design Specifications](/design/design-moment/) as a reference.
-2. If the game only supports landscape _or_ portrait mode, you only need to provide one background image. Otherwise, you need to provide background images for both.
-3. Images are subject to review, which usually takes 2 business days.
-
-:::
-
-![](https://capacity-files.lcfile.com/tAQ4vo4oypHCWQgVxRrSdjdR79wc88zn/io-tapmoment-theme-config.png)
-
-### Banner Configuration
-
-You can set up banners in Embedded Moments to help you broadcast your events to the players. To set up banners, go to “Game Services” > “Embedded Moments” > “Banner Configuration”. **A title, background image, and link** are required for each banner.
-
-:::tip
-
-1. You can add up to 5 banners that link to any website.
-2. Banners are subject to review, which usually takes less than one day.
-
-:::
-
-![](https://capacity-files.lcfile.com/NcVau0qn8pM93pDxXXfFa651SKPyoDgS/io-banner-config.png)
-
-
-### Scenario-Based Portal Configuration
-
-You can set up scenario-based portals in “Game Services” > “Embedded Moments” > “Scenario-based Portal”. Once you submit a **portal name, landing page type, and landing page**, you can use the generated portal ID in your game. This module doesn’t require any reviews, and you are free to change the landing page of each portal.
-
-![](https://capacity-files.lcfile.com/tgh3I8pB3R8bVlxTYQHA9lj7TOYeETvV/io-scenario-based-portal-configuration.png)
-
-## Group Management Center
-
-![](https://capacity-files.lcfile.com/NblfCHQ5MVVAO54TqJKRAHqnh6Isk9m9/forum-management.png)
-
-### Group Data
-
-To help you better assess the value of Embedded Moments and get feedback about the quality of content, you can view data associated with Embedded Moments through “Game Services” > “Embedded Moments” > “Group Management Center”. Make sure you have the permission required to view such data.
-
-### Recommendations
-
-You can set up recommendations in “Group Management Center” > “Recommendation”. **A title, image, and link** are required for each recommendation. Adding and editing recommendations are subject to review.
-
-### Sub-Groups
-
-You can set up sub-groups in “Group Management Center” > “Sub-group”. **A name (in multiple languages)** is required for each sub-group. Adding and editing sub-groups are subject to review.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/embedded-moments/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/embedded-moments/guide.mdx
deleted file mode 100644
index 9c35a423d..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/embedded-moments/guide.mdx
+++ /dev/null
@@ -1,438 +0,0 @@
----
-title: Embedded Moments Guide
-sidebar_label: Guide
-sidebar_position: 2
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-
-In this article, we will introduce how you can add [TapTap Embedded Moments](/sdk/embedded-moments/features/) to your game. Embedded Moments depends on TapTap Login, which requires the `TapMoment` module.
-
-## Installing SDK
-
-:::info
-
-- If you are [using TapTap Login without other TDS cloud services](/sdk/taptap-login/guide/tap-login/), please see [Using Embedded Moments Without TDS Cloud Services](#using-embedded-moments-without-tds-cloud-services) for how to initialize your app and install the SDK.
-
-:::
-
-If you are coming from [TapSDK Quickstart](/sdk/start/quickstart/#initialization) and have already initialized the SDK, you can add the `TapMoment` module of the TapSDK obtained from the [Downloads](/tap-download) page:
-
-
-
-
- {`"dependencies":{
- ...
- // Embedded Moments
- "com.taptap.tds.moment":"https://github.com/TapTap/TapMoment-Unity.git#${sdkVersions.taptap.unity}",
-}`}
-
-
-
- {`repositories{
- flatDir {
- dirs 'libs'
- }
-}
-dependencies {
- ...
- implementation (name:'TapMoment_${sdkVersions.taptap.android}', ext:'aar') // TapTap Embedded Moments
-}`}
-
-
-
- {`// Embedded Moments
-TapMomentResource.bundle
-TapMomentSDK.framework
-`}
-
-
-
-
-## Setting up Callbacks
-
-You can set up a callback to capture status updates:
-
-
-
-```cs
-TapMoment.SetCallback((code, msg) => {
- Debug.Log(code + "---" + msg);
-});
-```
-
-```java
-TapMoment.setCallback(new TapMoment.TapMomentCallback() {
- @Override
- public void onCallback(int code, String msg) {
-
- }
-});
-```
-
-```objectivec
-@interface ViewController ()
-@end
-
-[TapMoment setDelegate:self];
-- (void)onMomentCallbackWithCode:(NSInteger)code msg:(NSString *)msg
-{
- NSLog (@"msg:%@, code:%i", msg, code);
-}
-```
-
-
-
-The `code` in the callback function refers to the type of the event. The following types of events are supported:
-
-| Callback | Value | Description |
-| -------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------- |
-| CALLBACK_CODE_PUBLISH_SUCCESS | 10000 | Moment posted successfully. |
-| CALLBACK_CODE_PUBLISH_FAIL | 10100 | Failed to post the moment. |
-| CALLBACK_CODE_PUBLISH_CANCEL | 10200 | The page for posting moments is closed. |
-| CALLBACK_CODE_GET_NOTICE_SUCCESS | 20000 | Notifications retrieved successfully. |
-| CALLBACK_CODE_GET_NOTICE_FAIL | 20100 | Failed to retrieve notifications. |
-| CALLBACK_CODE_MOMENT_APPEAR | 30000 | Embedded Moments is opened. |
-| CALLBACK_CODE_MOMENT_DISAPPEAR | 30100 | Embedded Moments is closed. |
-| CALLBACK_CODE_CLOSE_CANCEL | 50000 | The user refused to close all the Embedded Moments pages (the “Cancel” button on the pop-up is tapped). |
-| CALLBACK_CODE_CLOSE_CONFIRM | 50100 | The user confirmed to close all the Embedded Moments pages (the “Confirm” button on the pop-up is tapped). |
-| CALLBACK_CODE_LOGIN_SUCCESS | 60000 | Logged in successfully. |
-| CALLBACK_CODE_SCENE_EVENT | 70000 | Callback for scenario-based portals. |
-
-## Retrieving Notifications
-
-You can periodically call the API to retrieve unread notifications. When there are unread notifications available, you can display a badge on the button for Embedded Moments to remind the player to check the notifications.
-
-
-
-```cs
-TapMoment.FetchNotification();
-```
-
-```java
-TapMoment.fetchNotification();
-```
-
-```objectivec
-[TapMoment fetchNotification];
-```
-
-
-
-The result of retrieving unread notifications will be available in the callback function mentioned earlier. If you get `CALLBACK_CODE_GET_NOTICE_SUCCESS` (`20000`) for the `code`, that means the notifications are retrieved successfully. `CALLBACK_CODE_GET_NOTICE_FAIL` (`20100`) means the SDK failed to retrieve the notifications. If the notifications are retrieved successfully, `msg` will be the number of unread notifications. When `msg` is `0`, it means there are no unread notifications.
-
-:::tip
-
-To make it easier for the player to view what you and their friends have posted, we suggest that you put the button for Embedded Moments on a prominent position in your game and retrieve unread notifications **once a minute**.
-
-When retrieving notifications, if there are no unread notifications (`msg` equals `0`), you can remove the badge displayed on the button. You should also remove the badge once the player opens Embedded Moments.
-
-:::
-
-## Displaying Embedded Moments
-
-You can call the following API to display Embedded Moments in your game. Here the player can view the moments posted by other players and post their own as well.
-
-
-
-```cs
-TapMoment.Open(Orientation.ORIENTATION_LANDSCAPE);
-```
-
-```java
-TapMoment.open(TapMoment.ORIENTATION_PORTRAIT);
-```
-
-```objectivec
-TapMomentConfig *mConfig = TapMomentConfig.new;
-mConfig.orientation = TapMomentOrientationDefault;
-[TapMoment open:mConfig];
-```
-
-
-
-:::note
-
-Embedded Moments might contain videos that have sounds. Therefore, please mute the sounds from the game itself when the Embedded Moments is opened.
-
-To make Embedded Moments rotate along with the device, your game has to support rotation as well.
-
-Don’t forget to remove the badge after the player opens the Embedded Moments.
-
-:::
-
-The following picture shows how you can customize the background image of the Embedded Moments page in your game. The background image will be reviewed before the players can see them.
-
-![](https://capacity-files.lcfile.com/tAQ4vo4oypHCWQgVxRrSdjdR79wc88zn/io-tapmoment-theme-config.png)
-
-## Scenario-Based Portals
-
-With [Scenario-Based Portals](/sdk/embedded-moments/features/#scenario-based-portals), the player can be taken to specific pages when they open Embedded Moments through them. Make sure to [set up Scenario-Based Portals](/sdk/embedded-moments/features/#scenario-based-portal-configuration) in TapTap Developer Center before you use this feature.
-
-
-<>
-
-```cs
-var sceneDic = new Dictionary() { { TapMomentConstants.TapMomentPageShortCutKey, sceneId } };
-// sceneId is the “portal ID” generated when you create a scenario-based portal in TapTap Developer Center
-TapMoment.DirectlyOpen(Orientation.ORIENTATION_DEFAULT, TapMomentConstants.TapMomentPageShortCut, sceneDic);
-```
-
-#### Arguments
-
-| Argument | Description |
-| ----------- | ------------------------------------------------------------------------------------------------------------------------ |
-| orientation | The screen orientation for displaying the Embedded Moments. |
-| page | Should be `TapMomentConstants.TapMomentPageShortCut`. |
-| Dictionary | Leave `TapMomentConstants.TapMomentPageShortCutKey` as it is and change the third argument to the ID of the target page. |
-
->
-<>
-
-```java
-Map extras = new HashMap<>();
-// Note: The key is always "scene_id" and the second argument is the “portal ID” generated when you create a scenario-based portal in TapTap Developer Center
-extras.put("scene_id", "xxxx");
-// Note: The second argument is always "tap://moment/scene/"
-TapMoment.directlyOpen(TapMoment.ORIENTATION_DEFAULT,"tap://moment/scene/", extras);
-```
-
-#### Arguments
-
-| Argument | Description |
-| ----------- | ----------------------------------------------------------------------- |
-| orientation | The screen orientation for displaying the Embedded Moments. |
-| page | Should be `tap://moment/scene/`. |
-| HashMap | The key is always `scene_id` which indicates the ID of the target page. |
-
->
-<>
-
-```objectivec
-TapMomentConfig *mConfig = TapMomentConfig.new;
-mConfig.orientation = TapMomentOrientationDefault;
-[TapMoment directlyOpen:mConfig page:TapMomentPageShortcut extras:@{ TapMomentPageShortcutKey: @"sceneid" }];
-```
-
-#### Arguments
-
-| Argument | Description |
-| ----------- | --------------------------------------------------------------- |
-| orientation | The screen orientation for displaying the Embedded Moments. |
-| page | Should be `TapMomentPageShortcut`. |
-| Dictionary | `TapMomentPageShortcutKey` indicates the ID of the target page. |
-
->
-
-
-
-### Callbacks for Scenario-Based Portals
-
-**Properties**
-
-| Field name | Type | Required | Description |
-| ------------ | ------- | -------- | ---------------------------------------------------------- |
-| sceneId | String | Yes | The ID of the scenario-based portal. |
-| eventType | String | Yes | The type of the event, like `VIEW`, `FORWARD`, and `VOTE`. |
-| eventPayload | String | Yes | Custom JSON string depending on the event type. |
-| timestamp | Integer | Yes | UNIX timestamp in ms. |
-
-**Event types**
-
-| eventType | eventPayload (unserialized) | Description |
-| --------- | --------------------------- | ------------------------------------------------- |
-| READY | {} | The DOM has been mounted and the data is pending. |
-| REPOST | {} | The user reposts a post. |
-| VOTE | { isCancel: boolean } | The user likes (or removes their like on) a post. |
-| FOLLOW | { isCancel: boolean } | The user follows (or unfollows) a post. |
-| COMMENT | {} | The user comments on a post. |
-
-## Closing Embedded Moments
-
-The player can close the Embedded Moments at any time to return to the game. In certain scenarios, the game itself can also request to close the Embedded Moments.
-
-A case for the game to initiate a request to close the Embedded Moments is when the player is matched with another player to start a battle. Here you can confirm with the player whether they want to stay in the Embedded Moments or return to the game immediately.
-
-
-
-```cs
-TapMoment.Close("Are you ready?", "The game is ready to start.");
-```
-
-```java
-TapMoment.closeWithConfirmWindow("Are you ready?", "The game is ready to start.");
-```
-
-```objectivec
-[TapMoment closeWithTitle:@"Are you ready?" content:@"The game is ready to start." showConfirm:YES];
-```
-
-
-
-The player’s selection will be returned with a callback:
-
-- `CALLBACK_CODE_CLOSE_CANCEL` (50000) means the player selected “Cancel” and wants to stay in Embedded Moments.
-- `CALLBACK_CODE_CLOSE_CONFIRM` (50100) means the player selected “Confirm” and wants to return to the game.
-
-To close the Embedded Moments without confirming:
-
-
-
-```cs
-TapMoment.Close();
-```
-
-```java
-TapMoment.close();
-```
-
-```objectivec
-[TapMoment close];
-```
-
-
-
-## Quick Sharing
-
-:::info
-
-This feature is optional. You can choose whether or not to enable this feature in your game.
-
-:::
-
-In general, the play can post their moments on the Embedded Moments page. But if you’d like, you can also allow the player to post moments containing images and texts without leaving the game.
-
-
-
-```cs
-string content = "This is the content";
-string[] images = {"imgpath01","imgpath02","imgpath03"};
-TapMoment.Publish(Orientation.ORIENTATION_LANDSCAPE, images, content);
-```
-
-```java
-int orientation = TapMoment.ORIENTATION_PORTRAIT;
-String content = "This is the content";
-String[] imagePaths = new String[]{"content://hello.jpg", "/sdcard/world.jpg"};
-TapMoment.publish(orientation, imagePaths, content);
-```
-
-```objectivec
-TapMomentConfig * tconfig = TapMomentConfig.new;
-tconfig.orientation = TapMomentOrientationDefault;
-
-TapMomentImageData *postData = TapMomentImageData.new;
-postData.images = @[@"file://..."];
-postData.content = @"This is the content";
-[TapMoment publish:tconfig content:(postData)];
-```
-
-
-
-:::info
-
-The player can post texts, images, and videos on the Embedded Moments page. However, they can only post texts and images with quick sharing.
-
-:::
-
-## Using Embedded Moments Without TDS Cloud Services
-
-See the following instructions if you are [using TapTap Login without TDS cloud services](/sdk/taptap-login/guide/tap-login/).
-
-1. Please first [download](/tap-download) the TapSDK and add the required dependencies. Embedded Moments depends on `TapLogin`, `TapCommon`, and `TapMoment`.
-
-If you use TapSDK Unity v3.7.1 or a higher version, make sure that you add the `com.leancloud.storage` module.
-
-
-
-
- {`"dependencies":{
- "com.taptap.tds.login":"https://github.com/TapTap/TapLogin-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.common":"https://github.com/TapTap/TapCommon-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.moment":"https://github.com/TapTap/TapMoment-Unity.git#${sdkVersions.taptap.unity}",
- "com.leancloud.storage": "https://github.com/leancloud/csharp-sdk-upm.git#storage-${sdkVersions.leancloud.csharp}",
-}`}
-
-
-
- {`repositories{
- flatDir {
- dirs 'libs'
- }
-}
-dependencies {
- ...
- implementation (name:'TapCommon_${sdkVersions.taptap.android}', ext:'aar') // Required: TapSDK essentials
- implementation (name:'TapLogin_${sdkVersions.taptap.android}', ext:'aar') // Required: TapTap Login
- implementation (name:'TapMoment_${sdkVersions.taptap.android}', ext:'aar') // TapTap Embedded Moments
-}`}
-
-
-
- {`// Essentials
-TapCommonSDK.framework
-TapLoginSDK.framework
-TapCommonResource.bundle
-TapLoginResource.bundle
-// Embedded Moments
-TapMomentResource.bundle
-TapMomentSDK.framework
-`}
-
-
-
-
-2. Make sure you have finished [initializing TapTap Login](/sdk/taptap-login/guide/tap-login/#initialization).
-
-3. To **initialize** Embedded Moments:
-
-
-<>
-
-```cs
-TapMoment.Init(string clientID, boolean isCN);
-```
-
-**Arguments**
-
-| Argument | Description |
-| -------- | --------------------------- |
-| clientID | The Client ID of your game. |
-| isCN | true for Mainland China; false for other countries/regions. |
-
->
-<>
-
-```java
-TapMoment.init(Context context, String clientID, boolean isCN);
-```
-
-**Arguments**
-
-| Argument | Description |
-| -------- | -------------------------------- |
-| context | Usually the current application. |
-| clientID | The Client ID of your game. |
-| isCN | true for Mainland China; false for other countries/regions. |
-
->
-<>
-
-```objectivec
-[TapMoment initWithClientID:@"your clientId" isCN:isCN];
-```
-
-**Arguments**
-
-| Argument | Description |
-| -------- | --------------------------- |
-| clientId | The Client ID of your game. |
-| isCN | true for Mainland China; false for other countries/regions. |
-
->
-
-
-
-4. Now you’re good to use the other interfaces mentioned on this page.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/gamesaves/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/gamesaves/_category_.json
deleted file mode 100644
index 7226c3acc..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/gamesaves/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "云存档",
- "collapsed": true,
- "position": 9
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/gamesaves/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/gamesaves/features.mdx
deleted file mode 100644
index abb86de4a..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/gamesaves/features.mdx
+++ /dev/null
@@ -1,48 +0,0 @@
----
-title: Cloud Save Introduction
-sidebar_label: Introduction
-sidebar_position: 1
----
-
-With Cloud Save, you can let your game save the players’ progress on the cloud as checkpoints. Your game can then retrieve those checkpoints from the cloud and let players continue from any checkpoint saved from any device.
-
-Cloud Save allows you to synchronize players’ progress across multiple devices. For example, with Cloud Save, if you have an Android game, your player can start the game on a phone and continue the game on a tablet without losing their progress. Even when the player lost their device, damaged their device, or got a new device, they can still continue the game from where they left off.
-
-To learn how to add Cloud Save into your game, see [Cloud Save Guide](/sdk/gamesaves/guide/).
-
-## The Basics
-
-Each checkpoint is made up of two parts:
-
-- A binary file containing the progress, which your game can read and update.
-- [Metadata](/sdk/gamesaves/features#metadata) holding some useful information related to this checkpoint.
-
-## Cover Image
-
-Each checkpoint has a cover image as part of its metadata. We strongly recommend that you pick an image that best describes the checkpoint.
-
-## Checkpoint Summary
-
-You can assign a brief summary to each checkpoint. The summary is intended to be seen by the player, so it should reflect the status represented by the checkpoint. An example would be “Fighting with the black-clad man on the rooftop”.
-
-## Resolving Conflicts
-
-If a player runs multiple instances of your game on different devices at the same time, there could be conflicts when these instances try to save the progress data to the cloud. Your app should resolve such conflicts in a manner that provides the best user experience to the players.
-
-Conflicts usually happen when your app tries to load or save data but is unable to make a connection to the cloud. The best way to avoid conflicts is to always have the app load the latest data from the cloud when it is opened or resumed, as well as regularly save the local data to the cloud. Your app should make the best attempt to resolve such conflicts in order to preserve players’ data and bring the best experience to the players.
-
-## Metadata
-
-The metadata of each checkpoint contains the following fields:
-
-| Field meaning | Field name | Required | Description |
-| ------------------ | --------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| Checkpoint ID | `objectId` | Automatically generated | A unique ID generated by the cloud for the checkpoint. Use this ID to reference the checkpoint in your game. |
-| Associated user | `user` | Automatically obtained | The SDK automatically associates the checkpoint with the current user. |
-| Original file | `gameFile` | Required | The original file containing the progress. |
-| Checkpoint name | `name` | Required | A brief name assigned to the checkpoint. The name is not visible to the player. You can group checkpoints by their names if you assign a custom rule to the names. |
-| Checkpoint summary | `summary` | Required | A string \*_no longer than 1000 characters_. You can assign each checkpoint a summary that is visible to the player. |
-| Modified at | `modifiedAt` | Required | The time the original file is modified or added. |
-| Time played | `playedTime` | Optional | The time the player has spent on the game in milliseconds. |
-| Progress | `progressValue` | Optional | An integer indicating the game progress. An example is the current level of the game. |
-| Cover image | `cover` | Optional | An [image](/sdk/gamesaves/features#cover-image) provided by you. At this time, you can only upload PNG and JPG files with the SDK. |
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/gamesaves/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/gamesaves/guide.mdx
deleted file mode 100644
index b6507f229..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/gamesaves/guide.mdx
+++ /dev/null
@@ -1,465 +0,0 @@
----
-title: Cloud Save Guide
-sidebar_label: Guide
-sidebar_position: 2
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-
-Before continuing, make sure you have read [Cloud Save Introduction](/sdk/gamesaves/features/) to learn about the basic concepts and features of Cloud Save.
-
-## Prerequisites
-
-Please complete the following steps before using Cloud Save:
-
-1. Add your [domains](/sdk/start/get-ready/#domains), including API domains and file domains, on the Developer Center.
-
-2. Enable **TDS Authentication** since the checkpoints will be associated with `TDSUser`s. Make sure you have completed [project configuration](/sdk/start/quickstart/#project-configuration), [SDK initialization](/sdk/start/quickstart/#initialization), and the implementation of [TDS Authentication](/sdk/authentication/guide/).
-
-## Creating Checkpoints
-
-The SDK will automatically get the information of the currently logged-in player (`TDSUser`) and associate it with the checkpoint.
-Therefore, a user can only create a checkpoint if they are logged in.
-
-
-
-```cs
-var gameSave = new TapGameSave
-{
- Name = "internal name",
- Summary = "description",
- ModifiedAt = DateTime.Now.ToLocalTime(),
- PlayedTime = 60000L, // ms
- ProgressValue = 100,
- CoverFilePath = image_local_path, // jpg/png
- GameFilePath = dll_local_path
-};
-await gameSave.Save();
-```
-
-```java
-TapGameSave snapshot = new TapGameSave();
-snapshot.setName("internal name");
-snapshot.setSummary("description");
-snapshot.setPlayedTime(60000); // ms
-snapshot.setProgressValue(100);
-snapshot.setCover(image_local_path); // jpg/png
-snapshot.setGameFile(dll_local_path);
-snapshot.setModifiedAt(new Date());
-snapshot.saveInBackground().subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable d) {}
-
- @Override
- public void onNext(@NotNull TapGameSave gameSave) {
- System.out.println("Checkpoint saved: " + gameSave.toJSONString());
- }
-
- @Override
- public void onError(@NotNull Throwable e) {
- e.printStackTrace();
- }
-
- @Override
- public void onComplete() {}
-});
-```
-
-```objc
-TapGameSave *gameSave = [TapGameSave new];
-gameSave.name = @"internal name";
-gameSave.summary = @"description";
-gameSave.modifiedAt = [NSDate date];
-gameSave.playedTime = 60000; // ms
-gameSave.progressValue = 100;
-[gameSave setCoverWithLocalPath:@"image_local_path" error:&error]; // jpg/png
-[gameSave setGameFileWithLocalPath:@"dll_local_path" error:&error];
-[gameSave saveInBackgroundWithBlock:^(BOOL succeeded, NSError *_Nullable error) {
- if (succeeded) {
- NSLog(@"Checkpoint saved. objectId: %@", gameSave.objectId);
- } else {
- // Error handling
- }
-}];
-```
-
-
-
-See [Metadata](/sdk/gamesaves/features#metadata) to learn more about the fields of metadata appearing in the example above.
-When you save a checkpoint, the SDK will ensure that only the current user can read and write into the checkpoint as well as the associated file and the cover image.
-
-## Retrieving Checkpoints
-
-The most common scenario is to retrieve all the checkpoints belonging to the current player:
-
-
-
-<>
-
-```cs
-var collection = await TapGameSave.GetCurrentUserGameSaves();
-
-foreach(var gameSave in collection){
- var summary = gameSave.Summary;
- var modifiedAt = gameSave.ModifiedAt;
- var playedTime = gameSave.PlayedTime;
- var progressValue = gameSave.ProgressValue;
- var coverFile = gameSave.Cover;
- var gameFile = gameSave.GameFile;
- var gameFileUrl = gameFile.Url;
-}
-```
-
-`gameFile.Url` is the URL of the file stored on the cloud. The extension of the downloaded file will be the same as that of the uploaded file.
-
->
-<>
-
-```java
-TapGameSave.getCurrentUserGameSaves()
- .subscribe(new Observer>() {
- @Override
- public void onSubscribe(@NotNull Disposable d) {}
-
- @Override
- public void onNext(@NotNull List tapGameSaves) {
- for (TapGameSave gameSave : tapGameSaves) {
- String summary = gameSave.getSummary();
- Date modifiedAt = gameSave.getModifiedAt();
- double playedTime = gameSave.getPlayedTime();
- int progressValue = gameSave.getProgressValue();
- LCFile cover = gameSave.getCover();
- LCFile gameFile = gameSave.getGameFile();
- }
- }
-
- @Override
- public void onError(@NotNull Throwable e) {
- e.printStackTrace();
- }
-
- @Override
- public void onComplete() {}
-});
-```
-
->
-<>
-
-```objc
-LCQuery *query = [TapGameSave queryWithCurrentUser];
-[query findObjectsInBackgroundWithBlock:^(NSArray *_Nullable gameSaves, NSError *_Nullable error) {
- if (error) {
- NSLog(@"test fail because %@", error);
- } else {
- for (TapGameSave *gameSave in gameSaves) {
- NSString *summary = gameSave.summary;
- NSDate *modifiedAt = gameSave.modifiedAt;
- double playedTime = gameSave.playedTime;
- int progressValue = gameSave.progressValue;
- LCFile* cover = gameSave.cover;
- LCFile* gameFile = gameSave.gameFile;
- }
- }
-}];
-```
-
->
-
-
-
-You can also retrieve checkpoints that meet certain criteria. For example, to retrieve all the current player’s checkpoints with progress larger than 3:
-
-
-
-<>
-
-```cs
-TDSUser user = await TDSUser.GetCurrent();
-LCQuery gameSaveQuery = TapGameSave.GetQueryWithUser(user);
-gameSaveQuery.WhereGreaterThan("progressValue", 3);
-var collections = await gameSaveQuery.Find();
-```
-
-See [Data Storage Guide](/sdk/storage/guide/dotnet/) for how to add constraints to queries.
-
->
-<>
-
-```java
-LCQuery gameSaveQuery = TapGameSave.getQueryWithUser();
-gameSaveQuery.whereGreaterThan("progressValue", 3);
-gameSaveQuery.findInBackground().subscribe(new Observer>() {
- public void onSubscribe(Disposable disposable) {}
- public void onNext(List gamesaves) {
- // gamesaves is an array containing checkpoint objects meeting the criteria
- }
- public void onError(Throwable throwable) {}
- public void onComplete() {}
-});
-```
-
-See [Data Storage Guide](/sdk/storage/guide/java/) for how to add constraints to queries.
-
->
-<>
-
-```objc
-LCQuery *query = [TapGameSave queryWithCurrentUser];
-[query whereKey:@"progressValue" greaterThan:@3];
-[query findObjectsInBackgroundWithBlock:^(NSArray *_Nullable gameSaves, NSError *_Nullable error) {
- // Things to do with the retrieved checkpoint objects
-}];
-```
-
->
-
-
-
-Notice that **if none of the players of your game have ever created a checkpoint, you will get an error when retrieving checkpoints that says** `Class or object doesn't exists.`.
-This is because the table (class) for storing checkpoint objects in our database only gets created when the first checkpoint object gets created.
-
-## Deleting Checkpoints
-
-A player can only delete their own checkpoints.
-
-To delete a checkpoint:
-
-
-
-```cs
-await gameSave.Delete();
-```
-
-```java
-gameSave.deleteInBackground().subscribe(new Observer() {
- @Override
- public void onSubscribe(@NonNull Disposable d) {}
-
- @Override
- public void onNext(LCNull response) {
- // Deleted.
- }
-
- @Override
- public void onError(@NonNull Throwable e) {
- System.out.println("Failed to delete:" + e.getMessage());
- }
-
- @Override
- public void onComplete() {}
-});
-```
-
-```objc
-[gameSave deleteInBackground];
-```
-
-
-
-When a checkpoint gets deleted, the associated cover image and the original file will also be deleted.
-
-## REST API
-
-Below are the REST API interfaces available for Cloud Save.
-You can write your own programs to access these interfaces and perform administrative operations on the server side.
-
-### Request Format
-
-For POST and PUT requests, the request body must be in JSON and the Content-Type of the HTTP Header must be `application/json`.
-
-Requests are authenticated by the key-value pairs in the HTTP Header shown in the following table:
-
-| Key | Value | Description | Source |
-| -------------- | ---------------- | ------------------------------------------------- | ------------------------------------ |
-| `X-LC-Id` | `{{appid}}` | The `App Id` (`Client Id`) of the current app | Can be found on the Developer Center |
-| `X-LC-Key` | `{{appkey}}` | The `App Key` (`Client Token`) of the current app | Can be found on the Developer Center |
-| `X-LC-Session` | `` | The player’s credential for logging in | |
-
-The `Master Key` is required for you to access the administration interface. Use `X-LC-Key: {{masterkey}},master` to have the server treat the given key as a master key.
-`Master Key` is also called `Server Secret`, which can be found on the Developer Center. When accessing the administration interface, `sessionToken` can be omitted.
-
-See [Credentials](/sdk/storage/guide/setup-dotnet#credentials) for more details.
-
-The cloud imposes restrictions on each checkpoint so that it can only be accessed by the player who created it. Therefore, when accessing the REST API, you have to either include a player’s `sessionToken` in the `X-LC-Session` HTTP header or use the `Master Key`, otherwise the request will fail due to a lack of permission.
-
-### Base URL
-
-The Base URL for REST API requests (the `{{host}}` in the curl examples) is the custom API domain of your app. You can update or find it on the Developer Center.
-See [Domain](/sdk/storage/guide/setup-dotnet#domain) for more details.
-
-### Interfaces
-
-| Name | Method | Address | Description |
-| --------------------- | ------ | ---------------- | ------------------------------------------------ |
-| Retrieve a checkpoint | GET | `/gamesaves/:id` | Retrieve a checkpoint by its ID. |
-| Retrieve checkpoints | GET | `/gamesaves` | Retrieve checkpoints that meet certain criteria. |
-| Add a checkpoint | POST | `/gamesaves` | Add a new checkpoint. |
-| Update a checkpoint | PUT | `/gamesaves/:id` | Update a checkpoint by its ID. |
-| Delete a checkpoint | DELETE | `/gamesaves/:id` | Delete a checkpoint by its ID. |
-
-### Retrieving a Checkpoint
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: " \
- https://API_BASE_URL/1.1/gamesaves/
-```
-
-Response:
-
-```json
-{
- "updatedAt": "2021-08-16T09:18:30.093Z",
- "progressValue": 123,
- "name": "dennis",
- "objectId": "611a2d65bcf94a3222b6d5f3",
- "createdAt": "2021-08-16T09:18:29.761Z",
- "gameFile": {
- "__type": "Pointer",
- "className": "_File",
- "objectId": "60d1af149be3180684000002"
- },
- "summary": "hello",
- "modifiedAt": {
- "__type": "Date",
- "iso": "2015-06-21T18:02:52.249Z"
- },
- "user": {
- "__type": "Pointer",
- "className": "_User",
- "objectId": "5b62c15a9f54540062427acc"
- }
-}
-```
-
-See [Metadata](/sdk/gamesaves/features#metadata) to learn more about the fields of metadata.
-
-### Retrieving Checkpoints
-
-Use `where` to specify the criteria:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: " \
- -H "Content-Type: application/json" \
- -G \
- --data-urlencode 'where={"progressValue":123}' \
- https://API_BASE_URL/1.1/gamesaves
-```
-
-Response:
-
-```json
-{
- "results": [
- {
- "updatedAt": "2021-08-16T09:30:20.643Z",
- "name": "dennis",
- "createdAt": "2021-08-16T09:30:20.643Z",
- "gameFile": {
- "__type": "Pointer",
- "className": "_File",
- "objectId": "60d1af149be3180684000002"
- },
- "summary": "hello",
- "modifiedAt": {
- "__type": "Date",
- "iso": "2015-06-21T18:02:52.249Z"
- },
- "objectId": "611a302cbcf94a3222b6d687"
- }
- ]
-}
-```
-
-See [Data Storage REST API](/sdk/storage/guide/rest#query-constraints) for more details about the usage of `where`.
-
-### Adding a Checkpoint
-
-See [Metadata](/sdk/gamesaves/features#metadata) to learn more about the required and optional fields of metadata.
-
-Before accessing this interface, please first create the [files](/sdk/storage/guide/rest/#creating-files) referenced by the `gameFile` and `cover` fields of the checkpoint. Make sure **the ACLs of the files are set to be accessible by the current user only**.
-
-```
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: " \
- -H "Content-Type: application/json" \
- -d '{
- "progressValue":123,
- "playedTime":1283490343,
- "name":"dennis",
- "gameFile":{"id":"55a39634e4b0ed48f0c1845c", "__type":"File"},
- "cover":{"id": "543cbaede4b07db196f50f3c", "__type": "File"},
- "summary":"hello",
- "modifiedAt":{"__type":"Date", "iso":"2015-06-21T18:02:52.249Z"}
- }' \
-https://API_BASE_URL/1.1/gamesaves
-```
-
-If the operation succeeded, the `objectId` and creation time of the checkpoint will be returned:
-
-```
-{"objectId":"611a3407bcf94a3222b6d789", "createdAt":"2021-08-16T09:46:47.290Z"}
-```
-
-If the operation failed, there will be an error, like:
-
-- `gameFile is required.`: The required field `gameFile` is not provided.
-- `Forbidden to add new fields by class '_GameSave' permissions.`: Custom fields are included in the request, which is not allowed yet.
-
-### Deleting a Checkpoint
-
-```
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: " \
- https://API_BASE_URL/1.1/gamesaves/
-```
-
-Response:
-
-```json
-{}
-```
-
-You can add a `where` condition when deleting a checkpoint so you won’t accidentally delete the wrong checkpoints.
-See [Conditional Deletions](/sdk/storage/guide/rest#conditional-deletions).
-
-When a checkpoint gets deleted, the associated cover image and the original file will also be deleted.
-
-### Updating a Checkpoint
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: " \
- -H "Content-Type: application/json" \
- -d '{"progressValue": 114514}' \
- https://API_BASE_URL/1.1/gamesaves/
-```
-
-If the operation succeeded, the `objectId` and creation time of the checkpoint will be returned:
-
-```json
-{
- "updatedAt": "2021-08-16T09:49:49.579Z",
- "objectId": "611a34bdbcf94a3222b6d7af"
-}
-```
-
-If the operation failed, there will be an error:
-
-- `Forbidden to add new fields by class '_GameSave' permissions.`: Custom fields are included in the request, which is not allowed yet.
-
-Notice that if you update the cover image or the original file of a checkpoint, the old files used as them will not be automatically deleted.
-You will need to manually [delete](/sdk/storage/guide/rest/#deleting-files) them.
-Therefore, we recommend that you update a checkpoint by deleting and recreating it instead of directly updating it. The interface for updating checkpoints is mainly used to serve administrative scenarios.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/_category_.json
deleted file mode 100644
index 7e7e6520d..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "即时通讯",
- "collapsed": true,
- "position": 18
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/best-practice/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/best-practice/_category_.json
deleted file mode 100644
index d61b21d85..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/best-practice/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "最佳实践",
- "collapsed": true,
- "position": 3
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/best-practice/hook-text-moderation.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/best-practice/hook-text-moderation.mdx
deleted file mode 100644
index d607b35aa..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/best-practice/hook-text-moderation.mdx
+++ /dev/null
@@ -1,94 +0,0 @@
----
-title: Text Moderation With Instant Messaging
-sidebar_label: Text Moderation
----
-
-This article introduces how you can integrate third-party text moderation services into your application using a hook offered by the Instant Messaging service.
-
-## Before You Start
-
-This article assumes that you already know about the hooks offered by the Instant Messaging service as well as Cloud Engine’s web interface for editing Cloud Functions. If you haven’t learned about them yet, please first take a look at the following pages:
-
-1. [Hooks and System Conversations](/sdk/im/guide/systemconv/)
-2. [Cloud Functions and Hooks Guide § Writing Cloud Functions Online](/sdk/engine/functions/guides#writing-cloud-functions-online)
-
-## Enabling the Feature
-
-1. With the Cloud Engine service enabled, go to **Cloud Engine > web > Deploy > Create function** and select `Hook` in the pop-up window. Select `_messageReceived` as the name of the hook and provide your code below. Once you finish creating the hook, click on **Deploy** and wait for the deployment to complete.
-
-![](https://capacity-files.lcfile.com/dmLYApCx0fTIx0FCsrJbs4PnXwPGkiA2/Frame%202%20%281%29.png)
-
-2. Now when a message is sent through the Instant Messaging service, you will see that the message gets changed according to the mechanism you set up.
-
-## Code Example
-
-Below is a code example for the hook written in Node.js. You can use it as a boilerplate for your code:
-
-```javascript
-const https = require("https");
-
-// Assuming that the third-party text moderation service requires authentication using the HTTP Header
-const authToken = "THE_TOKEN_FOR_THE_THIRD_PARTY_TEXT_MODERATION_SERVICE";
-
-const params = request.params;
-
-// Submit the message and the user ID to the third-party text moderation service
-// Different services may require different parameters
-const postData = JSON.stringify({
- data: {
- text: params.content,
- user_id: params.fromPeer,
- },
-});
-
-const options = {
- hostname: "third-party-text-moderation.example.com",
- port: 443,
- path: "/path/to/text/moderation/interface",
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-Third-Party-Auth": authToken,
- },
-};
-
-return new Promise((resolve, reject) => {
- const req = https.request(options, (res) => {
- if (res.statusCode != 200) {
- // or resolve(null)
- reject(new Error(`BAD STATUS: ${res.statusCode}`));
- return;
- }
- let body = "";
- res.setEncoding("utf8");
- res.on("data", (chunk) => {
- body += chunk;
- });
- res.on("end", () => {
- json = JSON.parse(body);
-
- if (json.result == 0) {
- resolve(null);
- } else {
- // Assuming that the third-party text moderation service will return the filtered text as the value of the `filtered_text` field
- if (json.filtered_text) {
- resolve({ content: json.filtered_text });
- } else {
- resolve({ drop: true });
- }
- }
- });
- });
-
- req.on("error", (e) => {
- // or resolve(null)
- reject(e);
- });
-
- req.write(postData);
- req.end();
-});
-```
-
-The code above shows how you can use a third-party text moderation service with the Instant Messaging service. You can customize the request and response according to your own requirements.
-To learn more about the parameters of the hook and the request and response parameters of the text moderation service, please refer to [the \_messageReceived hook of the Instant Messaging service](/sdk/im/guide/systemconv/#_messagereceived) and the API documentation of the text moderation service you are using.
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/best-practice/realtime-guide-onoff-status.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/best-practice/realtime-guide-onoff-status.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/best-practice/realtime-guide-onoff-status.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/im/best-practice/realtime-guide-onoff-status.mdx
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/features.mdx
deleted file mode 100644
index de049c87e..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/features.mdx
+++ /dev/null
@@ -1,41 +0,0 @@
----
-title: Instant Messaging Introduction
-sidebar_label: Introduction
-sidebar_position: 1
----
-
-With Instant Messaging, you can easily implement the commonly-used messaging features for socializing, live streaming, customer support, and online games.
-
-## Features
-
-Below are the main features provided by the Instant Messaging service:
-
-### Basic Features
-
-Add one-on-one chats, group chats, chat rooms, and system conversations into your app and allow users to send text messages, voice messages, videos, and locations. You can even create your own message types.
-
-### Authorization and Signing
-
-You can enable the third-party signing mechanism to verify the requests from the clients and ensure the data in your app is always secure.
-
-### Single-Device Sign-on and Push Notifications
-
-Decide whether a user can log in on multiple devices or just a single device at a time. Set up push notifications to get offline users notified of new messages.
-
-### Message Hooks
-
-Set up hooks on different stages of a message or conversation to extend our features.
-
-### Mentioning People, Recalling Messages, and Editing Messages
-
-Users can mention other people in a message. They can also recall or edit the messages they sent out. Messages can be cached on the local device for faster retrieval.
-
-## Why Choose Us
-
-- We provide comprehensive and flexible interfaces for you to quickly implement a fully-functional instant messaging function in your app. We offer common features like text moderation, as well as system conversations and hooks, which help you easily meet all types of messaging-related requirements.
-
-- We have served tens of thousands of products with some of them bearing tens of millions of DAU. We promise a 99.9% availability for our service.
-
-- Our service can handle more than 120 million messages per minute. If you ever plan to hold events that demand high concurrency, we’ve got you covered.
-
-- Our SDKs and demos are all open-source. If you ever get any questions, you can directly contact our engineers by submitting a ticket or asking on our forum. We also provide 24/7 phone support for emergencies.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/_category_.json
deleted file mode 100644
index 3446da847..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "开发指南",
- "collapsed": true,
- "position": 2
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/beginner.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/beginner.mdx
deleted file mode 100644
index 0204b8097..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/beginner.mdx
+++ /dev/null
@@ -1,5080 +0,0 @@
----
-title: 1. Basic Conversations and Messages
-sidebar_label: The Basics
-sidebar_position: 1
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import Mermaid from "/src/docComponents/Mermaid";
-
-## Introduction
-
-A lot of products today have the need to offer instant messaging functions to their users. For example:
-
-- To have the staff behind the product talk to the users.
-- To have the employees of a company communicate with each other.
-- To have the audience of a live stream interact with each other.
-- To have the users of an app or players of a game chat with each other.
-
-Based on the hierarchy of needs and the difficulty of implementation, we wrote four chapters of documentation for you to learn how you can embed Instant Messaging into your app:
-
-- In this chapter, we will introduce how you can implement one-on-one chats and group chats, how you can create and join conversations, and how you can send and receive rich media messages. We will also cover how history messages are kept on the cloud and how you can retrieve them. By the end of this chapter, you should be able to build a simple chatting page in your app.
-- [In the second chapter](/sdk/im/guide/intermediate/), we will introduce some advanced features built around messaging, including mentioning people with "@", recalling messages, editing messages, getting receipts when messages are delivered and read, sending push notifications, and synchronizing messages. The implementation of multi-device sign-on and custom message types will also be covered. By the end of this chapter, you should be able to integrate a chatting component into your app with these features.
-- [In the third chapter](/sdk/im/guide/senior/), we will introduce the security features offered by our services, including third-party signing mechanism. We will also go over the usage of chat rooms and temporary conversations. By the end of this chapter, you will get a set of skills to improve the security and usability of your app, as well as to build conversations that serve different purposes.
-- [In the last chapter](/sdk/im/guide/systemconv/), we will introduce the usage of hooks and system conversations, plus how you can build your own chatbots based on them. By the end of this chapter, you will learn how you can make your app extensible and adapted to a wide variety of requirements.
-
-We aim our documentation to not only help you complete the functions you are currently building but also give you a better understanding of all the things Instant Messaging can do, which you will find helpful when you plan to add more features to your app.
-
-Before you continue:
-
-Take a look at [Instant Messaging Overview](/sdk/im/guide/overview/) if you haven't done it yet.
-Also, make sure you have already installed and initialized the SDK for the platform (language) you are using:
-
-- [Installing C# SDK](/sdk/storage/guide/setup-dotnet/)
-- [Installing Java SDK](/sdk/storage/guide/setup-java/)
-- [Installing Objective-C SDK](/sdk/storage/guide/setup-objc/)
-
-## One-on-One Chats
-
-Before diving into the main topic, let's see what an `IMClient` object is in the Instant Messaging SDK:
-
-> An `IMClient` refers to an actual user, meaning that the user logged in to the system as a client.
-
-See [Instant Messaging Overview](/sdk/im/guide/overview/) for more details.
-
-### Creating `IMClient`
-
-Assuming that there is a user named "Tom". Now let's create an `IMClient` instance for him (make sure you have already initialized the SDK):
-
-
-
-```cs
-LCIMClient tom = new LCIMClient("Tom");
-```
-
-```java
-// clientId is Tom
-LCIMClient tom = LCIMClient.getInstance("Tom");
-```
-
-```objc
-// Define a property variable that persists in the memory
-@property (nonatomic) LCIMClient *tom;
-// Initialization
-NSError *error;
-tom = [[LCIMClient alloc] initWithClientId:@"Tom" error:&error];
-if (error) {
- NSLog(@"init failed with error: %@", error);
-} else {
- NSLog(@"init succeeded");
-}
-```
-
-```js
-// Tom logs in with his name as clientId
-realtime
- .createIMClient("Tom")
- .then(function (tom) {
- // Successfully logged in
- })
- .catch(console.error);
-```
-
-```swift
-// Define a global variable that persists in the memory
-var tom: IMClient
-// Initialization
-do {
- tom = try IMClient(ID: "Tom")
-} catch {
- print(error)
-}
-```
-
-```dart
-// clientId is Tom
-Client tom = Client(id: 'Tom');
-```
-
-
-
-Keep in mind that an `IMClient` refers to an actual user. It should be stored globally since all the further actions done by this user will have to access it.
-
-### Logging in to the Instant Messaging Server
-
-After creating the `IMClient` instance for Tom, we will need to have this instance log in to the Instant Messaging server.
-Only clients that are logged in can chat with other users and receive notifications from the cloud.
-
-For some SDKs (like the C# SDK), the client will be automatically logged in when the `IMClient` instance is created; for others (like iOS and Android SDKs), the client needs to be logged in manually with the `open` method:
-
-
-
-```cs
-await tom.Open();
-```
-
-```java
-// Tom creates a client and logs in with his name as clientId
-LCIMClient tom = LCIMClient.getInstance("Tom");
-// Tom logs in
-tom.open(new LCIMClientCallback() {
- @Override
- public void done(LCIMClient client, LCIMException e) {
- if (e == null) {
- // Successfully connected
- }
- }
-});
-```
-
-```objc
-// Define a property variable that persists in the memory
-@property (nonatomic) LCIMClient *tom;
-// Initialize and log in
-NSError *error;
-tom = [[LCIMClient alloc] initWithClientId:@"Tom" error:&error];
-if (error) {
- NSLog(@"init failed with error: %@", error);
-} else {
- [tom openWithCallback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- // open succeeded
- }
- }];
-}
-```
-
-```js
-// Tom logs in with his name as clientId and gets the IMClient instance
-realtime
- .createIMClient("Tom")
- .then(function (tom) {
- // Successfully logged in
- })
- .catch(console.error);
-```
-
-```swift
-// Define a global variable that persists in the memory
-var tom: IMClient
-// Initialize and log in
-do {
- tom = try IMClient(ID: "Tom")
- tom.open { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-// Tom creates a client and logs in with his name as clientId
-Client tom = Client(id: 'Tom');
-// Tom logs in
-await tom.open();
-```
-
-
-
-### Logging in with `_User`
-
-Besides specifying a `clientId` on the application layer, you can also log in directly by creating an `IMClient` with a `_User` object. By doing so, the signing process for logging in can be skipped which helps you easily integrate Data Storage with Instant Messaging:
-
-
-
-```cs
-var user = await LCUser.Login("USER_NAME", "PASSWORD");
-var client = new LCIMClient(user);
-```
-
-```java
-//Log in to the Data Storage service with the username and password of an LCUser
-LCUser.logIn("Tom", "cat!@#123").subscribe(new Observer() {
- public void onSubscribe(Disposable disposable) {}
- public void onNext(LCUser user) {
- // Logged in successfully and connected to the server
- LCIMClient client = LCIMClient.getInstance(user);
- client.open(new LCIMClientCallback() {
- @Override
- public void done(final LCIMClient avimClient, LCIMException e) {
- // Do other stuff
- }
- });
- }
- public void onError(Throwable throwable) {
- // Failed to log in (possibly because the password is incorrect)
- }
- public void onComplete() {}
-});
-```
-
-```objc
-// Define a property variable that persists in the memory
-@property (nonatomic) LCIMClient *client;
-// Log the User in and use the logged in User to initialize the Client and log in to the Instant Messaging service
-[LCUser logInWithUsernameInBackground:USER_NAME password:PASSWORD block:^(LCUser * _Nullable user, NSError * _Nullable error) {
- if (user) {
- NSError *err;
- client = [[LCIMClient alloc] initWithUser:user error:&err];
- if (err) {
- NSLog(@"init failed with error: %@", err);
- } else {
- [client openWithCallback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- // open succeeded
- }
- }];
- }
- }
-}];
-```
-
-```js
-var AV = require("leancloud-storage");
-// Log in to Instant Messaging with the username and password of an AVUser
-AV.User.logIn("username", "password")
- .then(function (user) {
- return realtime.createIMClient(user);
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-// Define a global variable that persists in the memory
-var client: IMClient
-// Log the User in and use the logged in User to initialize the Client and log in to the Instant Messaging service
-LCUser.logIn(username: USER_NAME, password: PASSWORD) { (result) in
- switch result {
- case .success(object: let user):
- do {
- client = try IMClient(user: user)
- client.open { (result) in
- // handle result
- }
- } catch {
- print(error)
- }
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-### Creating Conversations
-
-A `Conversation` needs to be created before a user can chat with others.
-
-`Conversation`s are the carriers of messages. All the messages are sent to conversations to be delivered to the members in them.
-
-Since Tom is already logged in, he can start chatting with other users now. If he wants to chat with Jerry, he can create a `Conversation` containing Jerry and himself:
-
-
-
-```cs
-var conversation = await tom.CreateConversation(new string[] { "Jerry" }, name: "Tom & Jerry", unique: true);
-```
-
-```java
-tom.createConversation(Arrays.asList("Jerry"), "Tom & Jerry", null, false, true,
- new LCIMConversationCreatedCallback() {
- @Override
- public void done(LCIMConversation conversation, LCIMException e) {
- if(e == null) {
- // Successfully created
- }
- }
-});
-```
-
-```objc
-// Create a conversation with Jerry
-[self createConversationWithClientIds:@[@"Jerry"] callback:^(LCIMConversation * _Nullable conversation, NSError * _Nullable error) {
- // handle callback
-}];
-```
-
-```js
-// Create a conversation with Jerry
-tom
- .createConversation({
- // tom is an IMClient instance
- // Members of the conversation include Jerry (the SDK will automatically add the current user into the conversation) besides Tom
- members: ["Jerry"],
- // The name of the conversation
- name: "Tom & Jerry",
- unique: true,
- })
- .then(/* Do other stuff */);
-```
-
-```swift
-do {
- try tom.createConversation(clientIDs: ["Jerry"], name: "Tom & Jerry", isUnique: true, completion: { (result) in
- switch result {
- case .success(value: let conversation):
- print(conversation)
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- // Create a conversation with Jerry
- Conversation conversation = await tom.createConversation(
- isUnique: true, members: {'Jerry'}, name: 'Tom & Jerry');
-} catch (e) {
- print('Failed to create the conversation: $e');
-}
-```
-
-
-
-`createConversation` creates a new conversation and stores it into the `_Conversation` table which can be found on **Developer Center > Your game > Game Services > Cloud Services > Data Storage > Data**. Below are the interfaces offered by different SDKs for creating conversations:
-
-
-
-```cs
-///
-/// Creates a conversation
-///
-/// The list of clientIds of participants in this conversation (except the creator)
-/// The name of this conversation
-/// Whether this conversation is unique;
-/// if it is true and an existing conversation contains the same composition of members,
-/// the existing conversation will be reused, otherwise a new conversation will be created.
-/// Custom attributes of this conversation
-///
-public async Task CreateConversation(
- IEnumerable members,
- string name = null,
- bool unique = true,
- Dictionary properties = null) {
- return await ConversationController.CreateConv(members: members,
- name: name,
- unique: unique,
- properties: properties);
-}
-```
-
-```java
-/**
- * Create or find an existing conversation
- *
- * @param members The members of the conversation
- * @param name The name of the conversation
- * @param attributes Custom attributes
- * @param isTransient Whether the conversation is a chat room
- * @param isUnique Whether to return the existing conversation satisfying conditions
- * If false, create a new conversation
- * If true, find if there is an existing conversation satisfying conditions; if so, return the conversation, otherwise create a new conversation
- * If true, only members is the valid query condition
- * @param callback The callback after the conversation is created
- */
-public void createConversation(final List members, final String name,
- final Map attributes, final boolean isTransient, final boolean isUnique,
- final LCIMConversationCreatedCallback callback);
-/**
- * Create a conversation
- *
- * @param members The members of the conversation
- * @param attributes Custom attributes
- * @param isTransient Whether the conversation is a chat room
- * @param callback The callback after the conversation is created
- */
-public void createConversation(final List members, final String name,
- final Map attributes, final boolean isTransient,
- final LCIMConversationCreatedCallback callback);
-/**
- * Create a conversation
- *
- * @param conversationMembers The members of the conversation
- * @param name The name of the conversation
- * @param attributes Custom attributes
- * @param callback The callback after the conversation is created
- * @since 3.0
- */
-public void createConversation(final List conversationMembers, String name,
- final Map attributes, final LCIMConversationCreatedCallback callback);
-/**
- * Create a conversation
- *
- * @param conversationMembers The members of the conversation
- * @param attributes Custom attributes
- * @param callback The callback after the conversation is created
- * @since 3.0
- */
-public void createConversation(final List conversationMembers,
- final Map attributes, final LCIMConversationCreatedCallback callback);
-```
-
-```objc
-/// The option of conversation creation.
-@interface LCIMConversationCreationOption : NSObject
-/// The name of the conversation.
-@property (nonatomic, nullable) NSString *name;
-/// The attributes of the conversation.
-@property (nonatomic, nullable) NSDictionary *attributes;
-/// Create or get an unique conversation, default is `true`.
-@property (nonatomic) BOOL isUnique;
-/// The time interval for the life of the temporary conversation.
-@property (nonatomic) NSUInteger timeToLive;
-@end
-
-/// Create a Normal Conversation. Default is a Normal Unique Conversation.
-/// @param clientIds The set of client ID. it's the members of the conversation which will be created. the initialized members always contains the current client's ID. if the created conversation is unique, and the server has one unique conversation with the same members, that unique conversation will be returned.
-/// @param callback Result callback.
-- (void)createConversationWithClientIds:(NSArray *)clientIds
- callback:(void (^)(LCIMConversation * _Nullable conversation, NSError * _Nullable error))callback;
-
-/// Create a Normal Conversation. Default is a Normal Unique Conversation.
-/// @param clientIds The set of client ID. it's the members of the conversation which will be created. the initialized members always contains the current client's ID. if the created conversation is unique, and the server has one unique conversation with the same members, that unique conversation will be returned.
-/// @param option See `LCIMConversationCreationOption`.
-/// @param callback Result callback.
-- (void)createConversationWithClientIds:(NSArray *)clientIds
- option:(LCIMConversationCreationOption * _Nullable)option
- callback:(void (^)(LCIMConversation * _Nullable conversation, NSError * _Nullable error))callback;
-
-/// Create a Chat Room.
-/// @param callback Result callback.
-- (void)createChatRoomWithCallback:(void (^)(LCIMChatRoom * _Nullable chatRoom, NSError * _Nullable error))callback;
-
-/// Create a Chat Room.
-/// @param option See `LCIMConversationCreationOption`.
-/// @param callback Result callback.
-- (void)createChatRoomWithOption:(LCIMConversationCreationOption * _Nullable)option
- callback:(void (^)(LCIMChatRoom * _Nullable chatRoom, NSError * _Nullable error))callback;
-
-/// Create a Temporary Conversation. Temporary Conversation is unique in its Life Cycle.
-/// @param clientIds The set of client ID. it's the members of the conversation which will be created. the initialized members always contains this client's ID.
-/// @param callback Result callback.
-- (void)createTemporaryConversationWithClientIds:(NSArray *)clientIds
- callback:(void (^)(LCIMTemporaryConversation * _Nullable temporaryConversation, NSError * _Nullable error))callback;
-
-/// Create a Temporary Conversation. Temporary Conversation is unique in its Life Cycle.
-/// @param clientIds The set of client ID. it's the members of the conversation which will be created. the initialized members always contains this client's ID.
-/// @param option See `LCIMConversationCreationOption`.
-/// @param callback Result callback.
-- (void)createTemporaryConversationWithClientIds:(NSArray *)clientIds
- option:(LCIMConversationCreationOption * _Nullable)option
- callback:(void (^)(LCIMTemporaryConversation * _Nullable temporaryConversation, NSError * _Nullable error))callback;
-```
-
-```js
-/**
- * Create a conversation
- * @param {Object} options The fields beside the following ones will be treated as custom attributes
- * @param {String[]} options.members The members of the conversation; required; include the current client by default
- * @param {String} [options.name] The name of the conversation; optional; defaults to null
- * @param {Boolean} [options.transient=false] Whether the conversation is a chat room; optional
- * @param {Boolean} [options.unique=false] Whether the conversation is unique; if it is true and an existing conversation contains the same composition of members, the existing conversation will be reused, otherwise a new conversation will be created
- * @param {Boolean} [options.tempConv=false] Whether the conversation is temporary; optional
- * @param {Integer} [options.tempConvTTL=0] Optional; if tempConv is true, the TTL of the conversation can be specified here
- * @return {Promise.}
- */
-async createConversation({
- members: m,
- name,
- transient,
- unique,
- tempConv,
- tempConvTTL,
- // You may add more properties
-});
-```
-
-```swift
-/// Create a Normal Conversation. Default is a Unique Conversation.
-///
-/// - Parameters:
-/// - clientIDs: The set of client ID. it's the members of the conversation which will be created. the initialized members always contains the current client's ID. if the created conversation is unique, and the server has one unique conversation with the same members, that unique conversation will be returned.
-/// - name: The name of the conversation.
-/// - attributes: The attributes of the conversation.
-/// - isUnique: True means create or get a unique conversation, default is true.
-/// - completion: callback.
-public func createConversation(clientIDs: Set, name: String? = nil, attributes: [String : Any]? = nil, isUnique: Bool = true, completion: @escaping (LCGenericResult) -> Void) throws
-
-/// Create a Chat Room.
-///
-/// - Parameters:
-/// - name: The name of the chat room.
-/// - attributes: The attributes of the chat room.
-/// - completion: callback.
-public func createChatRoom(name: String? = nil, attributes: [String : Any]? = nil, completion: @escaping (LCGenericResult) -> Void) throws
-
-/// Create a Temporary Conversation. Temporary Conversation is unique in its Life Cycle.
-///
-/// - Parameters:
-/// - clientIDs: The set of client ID. it's the members of the conversation which will be created. the initialized members always contains this client's ID.
-/// - timeToLive: The time interval for the life of the temporary conversation.
-/// - completion: callback.
-public func createTemporaryConversation(clientIDs: Set, timeToLive: Int32, completion: @escaping (LCGenericResult) -> Void) throws
-```
-
-```dart
-/// To create a normal [Conversation].
-///
-/// [isUnique] is a special parameter, default is `true`, it affects the creation behavior and property [Conversation.isUnique].
-/// * When it is `true` and the relevant unique [Conversation] not exists in the server, this method will create a new unique [Conversation].
-/// * When it is `true` and the relevant unique [Conversation] exists in the server, this method will return that existing unique [Conversation].
-/// * When it is `false`, this method always create a new non-unique [Conversation].
-///
-/// [members] is the [Conversation.members].
-/// [name] is the [Conversation.name].
-/// [attributes] is the [Conversation.attributes].
-///
-/// Returns an instance of [Conversation].
-Future createConversation({
- bool isUnique = true,
- Set members,
- String name,
- Map attributes,
-}) async {}
-
-/// To create a new [ChatRoom].
-///
-/// [name] is the [Conversation.name].
-/// [attributes] is the [Conversation.attributes].
-///
-/// Returns an instance of [ChatRoom].
-Future createChatRoom({
- String name,
- Map attributes,
-}) async {}
-
-/// To create a new [TemporaryConversation].
-///
-/// [members] is the [Conversation.members].
-/// [timeToLive] is the [TemporaryConversation.timeToLive].
-///
-/// Returns an instance of [TemporaryConversation].
-Future createTemporaryConversation({
- Set members,
- int timeToLive,
-}) async {}
-```
-
-
-
-Although SDKs for different languages/platforms share different interfaces, they take in a similar set of parameters when creating a conversation:
-
-1. `members`: Required; includes the initial list of members in the conversation. The creator of the conversation is included by default, so `members` does not have to include the `clientId` of the current user.
-
-2. `name`: The name of the conversation; optional. The code above sets "Tom & Jerry" for it.
-
-3. `attributes`: The custom attributes of the conversation; optional. The code above does not specify any custom attributes. If you ever specify them for your conversations, you can retrieve them later with `LCIMConversation`. Such attributes will be stored in the `attr` field of the `_Conversation` table.
-
-4. `unique`/`isUnique` or `LCIMConversationOptionUnique`: Marks if the conversation is unique; optional.
-
- - If true, the cloud will perform a query on conversations with the list of members specified. If an existing conversation contains the same members, the conversation will be returned, otherwise a new conversation will be created.
- - If false, a new conversation will be created each time `createConversation` is called.
- - If not specified, it defaults to true.
- - In general, it is more reasonable that there is only one conversation existing for the same composition of members, otherwise it could be messy since multiple sets of message histories are available for the same group of people.
-
-5. Other parameters specifying the type of the conversation; optional. For example, `transient`/`isTransient` specifies if it is a chat room, and `tempConv`/`tempConvTTL` or `LCIMConversationOptionTemporary` specifies if it is a temporary conversation. If nothing is specified, it will be a basic conversation. We will talk more about them later.
-
-The built-in properties of a conversation can be retrieved once the conversation is created. For example, a globally unique ID will be created for each conversation which can be retrieved with `Conversation.id`. This is the field often used for querying conversations.
-
-### Sending Messages
-
-Now that the conversation is created, Tom can start sending messages to it:
-
-
-
-```cs
-var textMessage = new LCIMTextMessage("Get up, Jerry!");
-await conversation.Send(textMessage);
-```
-
-```java
-LCIMTextMessage msg = new LCIMTextMessage();
-msg.setText("Get up, Jerry!");
-// Send the message
-conversation.sendMessage(msg, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- if (e == null) {
- Log.d("Tom & Jerry", "Message sent!");
- }
- }
-});
-```
-
-```objc
-LCIMTextMessage *message = [LCIMTextMessage messageWithText:@"Get up, Jerry!" attributes:nil];
-[conversation sendMessage:message callback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"Message sent!");
- }
-}];
-```
-
-```js
-var { TextMessage } = require("leancloud-realtime");
-conversation
- .send(new TextMessage("Get up, Jerry!"))
- .then(function (message) {
- console.log("Tom & Jerry", "Message sent!");
- })
- .catch(console.error);
-```
-
-```swift
-do {
- let textMessage = IMTextMessage(text: "Get up, Jerry!")
- try conversation.send(message: textMessage) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- TextMessage textMessage = TextMessage();
- textMessage.text = 'Get up, Jerry!';
- await conversation.send(message: textMessage);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-The code above sends a message to the conversation specified. All the other members who are online will immediately receive the message.
-
-So how would Jerry see the message on his device?
-
-### Receiving Messages
-
-On another device, we create an `IMClient` with `Jerry` as `clientId` and log in to the server (just as how we did for Tom):
-
-
-
-```cs
-var jerry = new LCIMClient("Jerry");
-```
-
-```java
-// Jerry logs in
-LCIMClient jerry = LCIMClient.getInstance("Jerry");
-jerry.open(new LCIMClientCallback(){
- @Override
- public void done(LCIMClient client,LCIMException e){
- if(e==null){
- // Things to do after logging in
- }
- }
-});
-```
-
-```objc
-NSError *error;
-jerry = [[LCIMClient alloc] initWithClientId:@"Jerry" error:&error];
-if (!error) {
- [jerry openWithCallback:^(BOOL succeeded, NSError *error) {
- // handle callback
- }];
-}
-```
-
-```js
-var { Event } = require("leancloud-realtime");
-// Jerry logs in
-realtime
- .createIMClient("Jerry")
- .then(function (jerry) {})
- .catch(console.error);
-```
-
-```swift
-do {
- let jerry = try IMClient(ID: "Jerry")
- jerry.open { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-Client jerry = Client(id: 'Jerry');
-await jerry.open();
-```
-
-
-
-As the receiver of the message, Jerry doesn't have to create a conversation with Tom and may as well not know that Tom created a conversation with him. Jerry needs to set up a callback function to get notified of the things Tom did.
-
-By setting up callbacks, clients will be able to handle notifications sent from the cloud. Here we focus on the following two events:
-
-- The user is invited to a conversation. At the moment Tom creates a new conversation with Jerry, Jerry will receive a notification saying something like "Tom invited you to a conversation".
-- A new message is delivered to a conversation the user is already in. At the moment Tom sends out the message "Get up, Jerry!", Jerry will receive a notification including the message itself as well as the context information like the conversation the message is sent to and the sender of the message.
-
-Now let's see how clients should handle such notifications. The code below handles both "joining conversation" and "getting new message" events for Jerry:
-
-
-
-```cs
-jerry.OnInvited = (conv, initBy) => {
- WriteLine($"{initBy} invited Jerry to join the conversation {conv.Id}");
-};
-jerry.OnMessage = (conv, msg) => {
- if (msg is LCIMTextMessage textMessage) {
- // textMessage.ConversationId is the ID of the conversation the message belongs to
- // textMessage.Text is the content of the text message
- // textMessage.FromClientId is the clientId of the sender
- }
-};
-```
-
-```java
-// Java/Android SDK responds to notifications with custom event handlers
-public class CustomConversationEventHandler extends LCIMConversationEventHandler {
- /**
- * This method is implemented to handle the events when the current user is invited to a conversation
- *
- * @param client
- * @param conversation The conversation
- * @param operator The inviter
- * @since 3.0
- */
- @Override
- public void onInvited(LCIMClient client, LCIMConversation conversation, String invitedBy) {
- // Things to do after the current clientId (Jerry) is invited to the conversation
- }
-}
-// Set up global conversation event handler
-LCIMMessageManager.setConversationEventHandler(new CustomConversationEventHandler());
-
-// Java/Android SDK responds to notifications with custom event handlers
-public static class CustomMessageHandler extends LCIMMessageHandler{
- /**
- * Overloading this method to handle message receiving
- *
- * @param message
- * @param conversation
- * @param client
- */
- @Override
- public void onMessage(LCIMMessage message,LCIMConversation conversation,LCIMClient client){
- if(message instanceof LCIMTextMessage){
- Log.d(((LCIMTextMessage)message).getText()); // Get up, Jerry!
- }
- }
- }
-// Set up global message handling handler
-LCIMMessageManager.registerDefaultMessageHandler(new CustomMessageHandler());
-```
-
-```objc
-// Implement the LCIMClientDelegate delegate to respond to the notifications from the server
-// For those unfamiliar with the concept of delegation, please refer to:
-// https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html
-jerry.delegate = delegator;
-
-/*!
- The current user is added to a conversation
- @param conversation - The conversation
- @param clientId - The ID of the inviter
- */
-- (void)conversation:(LCIMConversation *)conversation invitedByClientId:(NSString *)clientId {
- NSLog(@"%@", [NSString stringWithFormat:@"The current clientId (Jerry) is invited by %@ to join the conversation.",clientId]);
-}
-
-/*!
- The current user receives a message
- @param conversation - The conversation
- @param message - The content of the message
- */
-- (void)conversation:(LCIMConversation *)conversation didReceiveTypedMessage:(LCIMTypedMessage *)message {
- NSLog(@"%@", message.text); // Get up, Jerry!
-}
-```
-
-```js
-// JS SDK responds to notifications by listening to event callbacks on the IMClient instance
-
-// The current user is added to a conversation
-jerry.on(Event.INVITED, function invitedEventHandler(payload, conversation) {
- console.log(payload.invitedBy, conversation.id);
-});
-
-// The current user receives a message; can be handled by responding to Event.MESSAGE
-jerry.on(Event.MESSAGE, function (message, conversation) {
- console.log("Message received: " + message.text);
-});
-```
-
-```swift
-let delegator: Delegator = Delegator()
-jerry.delegate = delegator
-
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case .message(event: let messageEvent):
- switch messageEvent {
- case .received(message: let message):
- print(message)
- default:
- break
- }
- default:
- break
- }
-}
-```
-
-```dart
-jerry.onMessage = ({
- Client client,
- Conversation conversation,
- Message message,
-}) {
- if (message.stringContent != null) {
- print('Received message: ${message.stringContent}');
- }
-};
-```
-
-
-
-With the two event handling functions above, Jerry will be able to receive messages from Tom. Jerry can send messages to Tom as well, as long as Tom has the same functions on his side.
-
-Now let's take a look at this sequence diagram showing how the first message sent from Tom to Jerry is processed:
-
->Cloud: 1. Tom adds Jerry into the conversation
-Cloud-->>Jerry: 2. Sends notification: you are invited to the conversation
-Jerry-->>UI: 3. Loads UI
-Tom->>Cloud: 4. Sends message
-Cloud-->>Jerry: 5. Sends notification: you have a new message
-Jerry-->>UI: 6. Shows the message
-`}
-/>
-
-Besides responding to notifications about new messages, clients also need to respond to those indicating the change of members in a conversation, like "XX invited XX into the conversation", "XX left the conversation", and "XX is removed by the admin".
-Such notifications will be delivered to clients in real time. See [Summary of Event Notifications Regarding Changes of Members](#summary-of-event-notifications-regarding-changes-of-members) for more details.
-
-## Group Chats
-
-We just discussed how we can create a conversation between two users. Now let's see how we can create a group chat with more people.
-
-There aren't many differences between the two types of conversations and a major one would be the number of members in them. You can either specify all the members of a group chat when creating it, or add them later after the conversation is created.
-
-### Creating Group Chats
-
-In the previous conversation between Tom and Jerry (assuming conversation ID to be `CONVERSATION_ID`), if Tom wants to add Mary into the conversation, the following code can be used:
-
-
-
-```cs
-// Get the conversation with ID
-var conversation = await tom.GetConversation("CONVERSATION_ID");
-// Invite Mary
-await conversation.AddMembers(new string[] { "Mary" });
-```
-
-```java
-// Get the conversation with ID
-final LCIMConversation conv = client.getConversation("CONVERSATION_ID");
-// Invite Mary
-conv.addMembers(Arrays.asList("Mary"), new LCIMOperationPartiallySucceededCallback() {
- @Override
- public void done(LCIMException e, List successfulClientIds, List failures) {
- // Member added
- }
-});
-```
-
-```objc
-// Get the conversation with ID
-LCIMConversationQuery *query = [self.client conversationQuery];
-[query getConversationById:@"CONVERSATION_ID" callback:^(LCIMConversation *conversation, NSError *error) {
- // Invite Mary
- [conversation addMembersWithClientIds:@[@"Mary"] callback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"Member added!");
- }
- }];
-}];
-```
-
-```js
-// Get the conversation with ID
-tom
- .getConversation("CONVERSATION_ID")
- .then(function (conversation) {
- // Invite Mary
- return conversation.add(["Mary"]);
- })
- .then(function (conversation) {
- console.log("Member added!", conversation.members);
- // The conversation now contains ['Mary', 'Tom', 'Jerry']
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- let conversationQuery = client.conversationQuery
- try conversationQuery.getConversation(by: "CONVERSATION_ID") { (result) in
- switch result {
- case .success(value: let conversation):
- do {
- try conversation.add(members: ["Mary"], completion: { (result) in
- switch result {
- case .allSucceeded:
- break
- case .failure(error: let error):
- print(error)
- case let .slicing(success: succeededIDs, failure: failures):
- if let succeededIDs = succeededIDs {
- print(succeededIDs)
- }
- for (failedIDs, error) in failures {
- print(failedIDs)
- print(error)
- }
- }
- })
- } catch {
- print(error)
- }
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-List conversations;
-try {
-// Get the conversation with ID
- ConversationQuery query = tom.conversationQuery();
- query.whereEqualTo('objectId', 'CONVERSATION_ID');
- conversations = await query.find();
-} catch (e) {
- print(e);
-}
-try {
- Conversation conversation = conversations.first;
-// Invite Mary
- MemberResult addResult = await conversation.addMembers(
- members: {'Mary'},
- );
-} catch (e) {
- print(e);
-}
-```
-
-
-
-On Jerry's side, he can add a listener for handling events regarding "new members being added". With the code below, he will be notified once Tom invites Mary to the conversation:
-
-
-<>
-
-```cs
-jerry.OnMembersJoined = (conv, memberList, initBy) => {
- WriteLine($"{initBy} invited {memberList} to join the conversation {conv.Id}");
-}
-```
-
-`AVIMOnInvitedEventArgs` contains the following fields:
-
-1. `InvitedBy`: The inviter
-2. `JoinedMembers`: The list of members being added
-3. `ConversationId`: The conversation
-
->
-<>
-
-```java
-public class CustomConversationEventHandler extends LCIMConversationEventHandler {
- /**
- * This method is implemented to handle the events when a new member joins a conversation
- *
- * @param client
- * @param conversation
- * @param members The list of new members
- * @param invitedBy The ID of the inviter; could be the new member itself
- * @since 3.0
- */
- @Override
- public void onMemberJoined(LCIMClient client, LCIMConversation conversation,
- List members, String invitedBy) {
- // Shows that Mary is added to 551260efe4b01608686c3e0f by Tom
- Toast.makeText(LeanCloud.applicationContext,
- members + " is added to " + conversation.getConversationId() + " by "
- + invitedBy, Toast.LENGTH_SHORT).show();
- }
-}
-// Set up global event handler
-LCIMMessageManager.setConversationEventHandler(new CustomConversationEventHandler());
-```
-
->
-<>
-
-```objc
-jerry.delegate = delegator;
-
-#pragma mark - LCIMClientDelegate
-/*!
- All members will receive a notification when a new member joins the conversation
- @param conversation - The conversation
- @param clientIds - The list of new members
- @param clientId - The ID of the inviter
- */
-- (void)conversation:(LCIMConversation *)conversation membersAdded:(NSArray *)clientIds byClientId:(NSString *)clientId {
- NSLog(@"%@", [NSString stringWithFormat:@"%@ is added to the conversation by %@",[clientIds objectAtIndex:0],clientId]);
-}
-```
-
->
-<>
-
-```js
-// A user is added to the conversation
-jerry.on(
- Event.MEMBERS_JOINED,
- function membersjoinedEventHandler(payload, conversation) {
- console.log(payload.members, payload.invitedBy, conversation.id);
- }
-);
-```
-
-`payload` contains the following fields:
-
-1. `members`: Array of strings; the list of `clientId`s of the members being added
-2. `invitedBy`: String; the `clientId` of the inviter
-
->
-<>
-
-```swift
-jerry.delegate = delegator
-
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case let .joined(byClientID: byClientID, at: atDate):
- print(byClientID)
- print(atDate)
- case let .membersJoined(members: members, byClientID: byClientID, at: atDate):
- print(members)
- print(byClientID)
- print(atDate)
- default:
- break
- }
-}
-```
-
->
-<>
-
-```dart
-// Get notified when someone joins a conversation
-jerry.onMembersJoined = ({
- Client client,
- Conversation conversation,
- List members,
- String byClientID,
- DateTime atDate,
-}) {
- print('${members.toString()} joined the conversation.');
-};
-```
-
->
-
-
-Here is the sequence diagram of the operation:
-
->Cloud: 1. Adds Mary
-Cloud->>Tom: 2. Sends notification: you invited Mary to the conversation
-Cloud-->>Mary: 2. Sends notification: you are added to the conversation by Tom
-Cloud-->>Jerry: 2. Sends notification: Mary is added to the conversation by Tom
-`}
-/>
-
-On Mary's side, to know that she is added to the conversation between Tom and Jerry, she can follow the way Jerry listens to the `INVITED` event, which can be found in [One-on-One Chats](#one-on-one-chats).
-
-If Tom wants to **create a new conversation with all the members included**, the following code can be used:
-
-
-
-```cs
-var conversation = await tom.CreateConversation(new string[] { "Jerry","Mary" }, name: "Tom & Jerry & friends", unique: true);
-```
-
-```java
-tom.createConversation(Arrays.asList("Jerry","Mary"), "Tom & Jerry & friends", null,
- new LCIMConversationCreatedCallback() {
- @Override
- public void done(LCIMConversation conversation, LCIMException e) {
- if (e == null) {
- // Conversation created
- }
- }
- });
-```
-
-```objc
-// Tom creates a conversation with his friends
-[tom createConversationWithClientIds:@[@"Jerry", @"Mary"] callback:^(LCIMConversation * _Nullable conversation, NSError * _Nullable error) {
- if (!error) {
- NSLog(@"Conversation created!");
- }
-}];
-```
-
-```js
-tom
- .createConversation({
- // Add Jerry and Mary to the conversation when creating it; more members can be added later as well
- members: ["Jerry", "Mary"],
- // The name of the conversation
- name: "Tom & Jerry & friends",
- unique: true,
- })
- .catch(console.error);
-```
-
-```swift
-do {
- try tom.createConversation(clientIDs: ["Jerry", "Mary"], name: "Tom & Jerry & friends", isUnique: true, completion: { (result) in
- switch result {
- case .success(value: let conversation):
- print(conversation)
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- Conversation conversation = await jerry.createConversation(
- isUnique: true,
- members: {'Jerry', 'Mary'},
- name: 'Tom & Jerry & friends');
-} catch (e) {
- print(e);
-}
-```
-
-
-
-### Sending Group Messages
-
-In a group chat, if a member sends a message, the message will be delivered to all the online members in the group. The process is the same as how Jerry receives the message from Tom.
-
-For example, if Tom sends a welcome message to the group:
-
-
-
-```cs
-var textMessage = new LCIMTextMessage("Welcome everyone!");
-await conversation.Send(textMessage);
-```
-
-```java
-LCIMTextMessage msg = new LCIMTextMessage();
-msg.setText("Welcome everyone!");
-// Send the message
-conversation.sendMessage(msg, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- if (e == null) {
- Log.d("Group chat", "Message sent!");
- }
- }
-});
-```
-
-```objc
-[conversation sendMessage:[LCIMTextMessage messageWithText:@"Welcome everyone!" attributes:nil] callback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"Message sent!");
- }
-}];
-```
-
-```js
-conversation.send(new TextMessage("Welcome everyone!"));
-```
-
-```swift
-do {
- let textMessage = IMTextMessage(text: "Welcome everyone!")
- try conversation.send(message: textMessage, completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- TextMessage textMessage = TextMessage();
- textMessage.text = 'Welcome everyone!';
- await conversation.send(message: textMessage);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-Both Jerry and Mary will have the `Event.MESSAGE` event triggered which can be used to retrieve the message and have it displayed on the UI.
-
-### Removing Members
-
-One day Mary said something that made Tom angry and Tom wants to kick her out of the group chat. How should Tom do that?
-
-
-
-```cs
-await conversation.RemoveMembers(new string[] { "Mary" });
-```
-
-```java
-conv.kickMembers(Arrays.asList("Mary"), new LCIMOperationPartiallySucceededCallback() {
- @Override
- public void done(LCIMException e, List successfulClientIds, List failures) {
- }
-});
-```
-
-```objc
-[conversation removeMembersWithClientIds:@[@"Mary"] callback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"Member removed!");
- }
-}];
-```
-
-```js
-conversation
- .remove(["Mary"])
- .then(function (conversation) {
- console.log("Member removed!", conversation.members);
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- try conversation.remove(members: ["Mary"], completion: { (result) in
- switch result {
- case .allSucceeded:
- break
- case .failure(error: let error):
- print(error)
- case let .slicing(success: succeededIDs, failure: failures):
- if let succeededIDs = succeededIDs {
- print(succeededIDs)
- }
- for (failedIDs, error) in failures {
- print(failedIDs)
- print(error)
- }
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- MemberResult removeMemberResult = await conversation.removeMembers(members: {'Mary'});
-} catch (e) {
- print(e);
-}
-```
-
-
-
-The following process will be triggered:
-
->Cloud: 1. Removes Mary
-Cloud-->>Mary: 2. Send notification: You are removed by Tom
-Cloud-->>Jerry: 2. Send notification: Mary is removed by Tom
-Cloud-->>Tom: 2. Send notification: Mary is removed
-`}
-/>
-
-Here we see that Mary receives `KICKED` which indicates that she (the current user) is removed. Other members (Jerry and Tom) will receive `MEMBERS_LEFT` which indicates that someone else in the conversation is removed. Such events can be handled with the following code:
-
-
-
-```cs
-jerry.OnMembersLeft = (conv, leftIds, kickedBy) => {
- WriteLine($"{leftIds} removed from {conv.Id} by {kickedBy}");
-}
-jerry.OnKicked = (conv, initBy) => {
- WriteLine($"You are removed from {conv.Id} by {initBy}");
-};
-```
-
-```java
-public class CustomConversationEventHandler extends LCIMConversationEventHandler {
- /**
- * This method is implemented to handle the events when someone is removed from a conversation
- *
- * @param client
- * @param conversation
- * @param members The members being removed
- * @param kickedBy The ID of the operator; could be the current user itself
- * @since 3.0
- */
- @Override
- public abstract void onMemberLeft(LCIMClient client,
- LCIMConversation conversation, List members, String kickedBy) {
- Toast.makeText(LeanCloud.applicationContext,
- members + " are removed from " + conversation.getConversationId() + " by "
- + kickedBy, Toast.LENGTH_SHORT).show();
- }
- /**
- * This method is implemented to handle the events when the current user is removed from a conversation
- *
- * @param client
- * @param conversation
- * @param kickedBy The person who removed you
- * @since 3.0
- */
- @Override
- public abstract void onKicked(LCIMClient client, LCIMConversation conversation,
- String kickedBy) {
- Toast.makeText(LeanCloud.applicationContext,
- "You are removed from " + conversation.getConversationId() + " by "
- + kickedBy, Toast.LENGTH_SHORT).show();
- }
-}
-// Set up global event handler
-LCIMMessageManager.setConversationEventHandler(new CustomConversationEventHandler());
-```
-
-```objc
-jerry.delegate = delegator;
-
-#pragma mark - LCIMClientDelegate
-/*!
- The remaining members in a conversation will receive this notification when someone is removed from the conversation.
- @param conversation - The conversation
- @param clientIds - The list of members being removed
- @param clientId - The ID of the operator
- */
-- (void)conversation:(LCIMConversation *)conversation membersRemoved:(NSArray * _Nullable)clientIds byClientId:(NSString * _Nullable)clientId {
- ;
-}
-/*!
- The notification for the events when the current user is removed from a conversation.
- @param conversation - The conversation
- @param clientId - The ID of the operator
- */
-- (void)conversation:(LCIMConversation *)conversation kickedByClientId:(NSString * _Nullable)clientId {
- ;
-}
-```
-
-```js
-// Someone else is removed
-jerry.on(
- Event.MEMBERS_LEFT,
- function membersjoinedEventHandler(payload, conversation) {
- console.log(payload.members, payload.kickedBy, conversation.id);
- }
-);
-// The current user is removed
-jerry.on(
- Event.KICKED,
- function membersjoinedEventHandler(payload, conversation) {
- console.log(payload.kickedBy, conversation.id);
- }
-);
-```
-
-```swift
-jerry.delegate = delegator
-
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case let .left(byClientID: byClientID, at: atDate):
- print(byClientID)
- print(atDate)
- case let .membersLeft(members: members, byClientID: byClientID, at: atDate):
- print(members)
- print(byClientID)
- print(atDate)
- default:
- break
- }
-}
-```
-
-```dart
-// Someone else is removed
-jerry.onMembersLeft = ({
- Client client,
- Conversation conversation,
- List members,
- String byClientID,
- DateTime atDate,
-}) {
- print('${members.toString()} is removed by $byClientID');
-};
-// The current user is removed
-jerry.onKicked = ({
- Client client,
- Conversation conversation,
- String byClientID,
- DateTime atDate,
-}) {
- print('You are removed by $byClientID');
-};
-```
-
-
-
-### Joining Conversations
-
-Tom is feeling bored after removing Mary. He goes to William and tells him that there is a group chat that Jerry and himself are in. He gives the ID (or name) of the group chat to William which makes him curious about what's going on in it. William then adds himself to the group:
-
-
-
-```cs
-var conv = await william.GetConversation("CONVERSATION_ID");
-await conv.Join();
-```
-
-```java
-LCIMConversation conv = william.getConversation("CONVERSATION_ID");
-conv.join(new LCIMConversationCallback(){
- @Override
- public void done(LCIMException e){
- if(e==null){
- // Successfully joined
- }
- }
-});
-```
-
-```objc
-LCIMConversationQuery *query = [william conversationQuery];
-[query getConversationById:@"CONVERSATION_ID" callback:^(LCIMConversation *conversation, NSError *error) {
- [conversation joinWithCallback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"Successfully joined!");
- }
- }];
-}];
-```
-
-```js
-william
- .getConversation("CONVERSATION_ID")
- .then(function (conversation) {
- return conversation.join();
- })
- .then(function (conversation) {
- console.log("Successfully joined!", conversation.members);
- // The conversation now contains ['William', 'Tom', 'Jerry']
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- let conversationQuery = client.conversationQuery
- try conversationQuery.getConversation(by: "CONVERSATION_ID") { (result) in
- switch result {
- case .success(value: let conversation):
- do {
- try conversation.join(completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
- } catch {
- print(error)
- }
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-List conversations;
-try {
- ConversationQuery query = william.conversationQuery();
- query.whereEqualTo('objectId', 'CONVERSATION_ID');
- conversations = await query.find();
-} catch (e) {
- print(e);
-}
-
-try {
- Conversation conversation = conversations.first;
- MemberResult joinResult = await conversation.join();
-} catch (e) {
- print(e);
-}
-```
-
-
-
-The following process will be triggered:
-
->Cloud: 1. Joins the conversations
-Cloud-->>William: 2. Sends notification: you joined the conversation
-Cloud-->>Tom: 2. Sends notification: William joined the conversation
-Cloud-->>Jerry: 2. Sends notification: William joined the conversation
-`}
-/>
-
-Other members can listen to `MEMBERS_JOINED` to know that William joined the conversation:
-
-
-
-```cs
-jerry.OnMembersJoined = (conv, memberList, initBy) => {
- WriteLine($"{memberList} joined {conv.Id}; operated by {initBy}");
-}
-```
-
-```java
-public class CustomConversationEventHandler extends LCIMConversationEventHandler {
- @Override
- public void onMemberJoined(LCIMClient client, LCIMConversation conversation,
- List members, String invitedBy) {
- // Shows that William joined 551260efe4b01608686c3e0f; operated by William
- Toast.makeText(LeanCloud.applicationContext,
- members + " joined " + conversation.getConversationId() + "; operated by "
- + invitedBy, Toast.LENGTH_SHORT).show();
- }
-}
-```
-
-```objc
-- (void)conversation:(LCIMConversation *)conversation membersAdded:(NSArray *)clientIds byClientId:(NSString *)clientId {
- NSLog(@"%@", [NSString stringWithFormat:@"%@ joined the conversation; operated by %@",[clientIds objectAtIndex:0],clientId]);
-}
-```
-
-```js
-jerry.on(
- Event.MEMBERS_JOINED,
- function membersJoinedEventHandler(payload, conversation) {
- console.log(payload.members, payload.invitedBy, conversation.id);
- }
-);
-```
-
-```swift
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case let .membersJoined(members: members, byClientID: byClientID, at: atDate):
- print(members)
- print(byClientID)
- print(atDate)
- default:
- break
- }
-}
-```
-
-```dart
-jerry.onMembersJoined = ({
- Client client,
- Conversation conversation,
- List members,
- String byClientID,
- DateTime atDate,
-}) {
- print('${members.toString()} joined');
-};
-```
-
-
-
-### Leaving Conversations
-
-With more and more people being invited by Tom, Jerry feels that he doesn't like most of them and wants to leave the conversation. He can do that with the following code:
-
-
-
-```cs
-await conversation.Quit();
-```
-
-```java
-conversation.quit(new LCIMConversationCallback(){
- @Override
- public void done(LCIMException e){
- if(e==null){
- // You left the conversation
- }
- }
-});
-```
-
-```objc
-[conversation quitWithCallback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"You left the conversation!");
- }
-}];
-```
-
-```js
-conversation
- .quit()
- .then(function (conversation) {
- console.log("You left the conversation!", conversation.members);
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- try conversation.leave(completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- MemberResult quitResult = await conversation.quit();
-} catch (e) {
- print(e);
-}
-```
-
-
-
-After leaving the conversation, Jerry will no longer receive messages from it. Here is the sequence diagram of the operation:
-
->Cloud: 1. Leaves the conversation
-Cloud-->>Jerry: 2. Sends notification: You left the conversation
-Cloud-->>Mary: 2. Sends notification: Jerry left the conversation
-Cloud-->>Tom: 2. Sends notification: Jerry left the conversation
-`}
-/>
-
-Other members can listen to `MEMBERS_LEFT` to know that Jerry left the conversation:
-
-
-
-```cs
-mary.OnMembersLeft = (conv, members, initBy) => {
- WriteLine($"{members} left {conv.Id}; operated by {initBy}");
-}
-```
-
-```java
-public class CustomConversationEventHandler extends LCIMConversationEventHandler {
- @Override
- public void onMemberLeft(LCIMClient client, LCIMConversation conversation, List members,
- String kickedBy) {
- // Things to do after someone left
- }
-}
-```
-
-```objc
-// If Mary is logged in, the following callback will be triggered when Jerry leaves the conversation
-- (void)conversation:(LCIMConversation *)conversation membersRemoved:(NSArray *)clientIds byClientId:(NSString *)clientId {
- NSLog(@"%@", [NSString stringWithFormat:@"%@ 离开了对话,操作者为:%@",[clientIds objectAtIndex:0],clientId]);
-}
-```
-
-```js
-mary.on(
- Event.MEMBERS_LEFT,
- function membersLeftEventHandler(payload, conversation) {
- console.log(payload.members, payload.kickedBy, conversation.id);
- }
-);
-```
-
-```swift
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case let .membersLeft(members: members, byClientID: byClientID, at: atDate):
- print(members)
- print(byClientID)
- print(atDate)
- default:
- break
- }
-}
-```
-
-```dart
-mary.onMembersLeft = ({
- Client client,
- Conversation conversation,
- List members,
- String byClientID,
- DateTime atDate,
-}) {
- print('${members.toString()} left');
-};
-```
-
-
-
-### Summary of Event Notifications Regarding Changes of Members
-
-The sequence diagrams displayed earlier already described what would happen when certain events are triggered. The table below serves as a summary of them.
-
-Assuming that Tom and Jerry are already in the conversation:
-
-
-<>
-
-| Operation | Tom | Jerry | Mary | William |
-| ---------------- | ----------------- | ----------------- | ----------- | ----------------- |
-| Tom invites Mary | `OnMembersJoined` | `OnMembersJoined` | `OnInvited` | / |
-| Tom removes Mary | `OnMembersLeft` | `OnMembersLeft` | `OnKicked` | / |
-| William joins | `OnMembersJoined` | `OnMembersJoined` | / | `OnMembersJoined` |
-| Jerry leaves | `OnMembersLeft` | `OnMembersLeft` | / | `OnMembersLeft` |
-
->
-<>
-
-| Operation | Tom | Jerry | Mary | William |
-| ---------------- | ---------------- | ---------------- | ----------- | ---------------- |
-| Tom invites Mary | `onMemberJoined` | `onMemberJoined` | `onInvited` | / |
-| Tom removes Mary | `onMemberLeft` | `onMemberLeft` | `onKicked` | / |
-| William joins | `onMemberJoined` | `onMemberJoined` | / | `onMemberJoined` |
-| Jerry leaves | `onMemberLeft` | `onMemberLeft` | / | `onMemberLeft` |
-
->
-
-<>
-
-| Operation | Tom | Jerry | Mary | William |
-| ---------------- | ---------------- | ------------------ | ------------------- | ---------------- |
-| Tom invites Mary | `membersAdded` | `membersAdded` | `invitedByClientId` | / |
-| Tom removes Mary | `membersRemoved` | `membersRemoved` | `kickedByClientId` | / |
-| William joins | `membersAdded` | `membersAdded` | / | `membersAdded` |
-| Jerry leaves | `membersRemoved` | `kickedByClientId` | / | `membersRemoved` |
-
->
-
-
-## Rich Media Messages
-
-We've seen how we can send messages containing plain text. Now let's see how we can send rich media messages like images, videos, and locations.
-
-The Instant Messaging service provides out-of-the-box support for messages containing text, files, images, audios, videos, locations, and binary data. All of them, except binary data, are sent as strings, though there are some slight differences between text messages and rich media messages (files, images, audios, and videos):
-
-- When sending text messages, the messages themselves are sent directly as strings.
-- When sending rich media messages (like images), the SDK will first upload the binary files to the cloud with the Data Storage service's `AVFile` interface, then embed the URLs of them into the messages being sent. We can say that **the essence of an image message is a text message holding the URL of the image**.
-
-Files stored on the Data Storage service have CDN enabled by default. Therefore, binary data (like images) are not directly encoded as part of text messages. This helps users access them faster and the cost on you can be lowered as well.
-
-### Default Message Types
-
-The following message types are offered by default:
-
-- `TextMessage` Text message
-- `ImageMessage` Image message
-- `AudioMessage` Audio message
-- `VideoMessage` Video message
-- `FileMessage` File message (.txt, .doc, .md, etc.)
-- `LocationMessage` Location message
-
-All of them are derived from `LCIMMessage`, with the following properties available for each:
-
-
-<>
-
-| Name | Type | Description |
-| ------------------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `content` | `String` | The content of the message. |
-| `clientId` | `String` | The `clientId` of the sender. |
-| `conversationId` | `String` | The ID of the conversation. |
-| `messageId` | `String` | A unique ID for each message. Assigned by the cloud automatically. |
-| `timestamp` | `long` | The time the message is sent. Assigned by the cloud automatically. |
-| `receiptTimestamp` | `long` | The time the message is delivered. Assigned by the cloud automatically. |
-| `status` | A member of `AVIMMessageStatus` | The status of the message. Could be one of:
`AVIMMessageStatusNone` (unknown) `AVIMMessageStatusSending` (sending) `AVIMMessageStatusSent` (sent) `AVIMMessageStatusReceipt` (delivered) `AVIMMessageStatusFailed` (failed) |
-| `ioType` | A member of `AVIMMessageIOType` | The direction of the message. Could be one of:
`AVIMMessageIOTypeIn` (sent to the current user) `AVIMMessageIOTypeOut` (sent by the current user) |
-
->
-<>
-
-| Name | Type | Description |
-| ------------------ | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `content` | `String` | The content of the message. |
-| `clientId` | `String` | The `clientId` of the sender. |
-| `conversationId` | `String` | The ID of the conversation. |
-| `messageId` | `String` | A unique ID for each message. Assigned by the cloud automatically. |
-| `timestamp` | `long` | The time the message is sent. Assigned by the cloud automatically. |
-| `receiptTimestamp` | `long` | The time the message is delivered. Assigned by the cloud automatically. |
-| `status` | A member of `MessageStatus` | The status of the message. Could be one of:
`StatusNone` (unknown) `StatusSending` (sending) `StatusSent` (sent) `StatusReceipt` (delivered) `StatusFailed` (failed) |
-| `ioType` | A member of `MessageIOType` | The direction of the message. Could be one of:
`TypeIn` (sent to the current user) `TypeOut` (sent by the current user) |
-
->
-<>
-
-| Name | Type | Description |
-| -------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `content` | `NSString` | The content of the message. |
-| `clientId` | `NSString` | The `clientId` of the sender. |
-| `conversationId` | `NSString` | The ID of the conversation. |
-| `messageId` | `NSString` | A unique ID for each message. Assigned by the cloud automatically. |
-| `sendTimestamp` | `int64_t` | The time the message is sent. Assigned by the cloud automatically. |
-| `deliveredTimestamp` | `int64_t` | The time the message is delivered. Assigned by the cloud automatically. |
-| `status` | A member of `AVIMMessageStatus` | The status of the message. Could be one of:
`LCIMMessageStatusNone` (unknown) `LCIMMessageStatusSending` (sending) `LCIMMessageStatusSent` (sent) `LCIMMessageStatusDelivered` (delivered) `LCIMMessageStatusFailed` (failed) |
-| `ioType` | A member of `LCIMMessageIOType` | The direction of the message. Could be one of:
`LCIMMessageIOTypeIn` (sent to the current user) `LCIMMessageIOTypeOut` (sent by the current user) |
-
->
-<>
-
-| Name | Type | Description |
-| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `from` | `String` | The `clientId` of the sender. |
-| `cid` | `String` | The ID of the conversation. |
-| `id` | `String` | A unique ID for each message. Assigned by the cloud automatically. |
-| `timestamp` | `Date` | The time the message is sent. Assigned by the cloud automatically. |
-| `deliveredAt` | `Date` | The time the message is delivered. |
-| `status` | `Symbol` | The status of the message. Could be one of the members of [`MessageStatus`](https://leancloud.github.io/js-realtime-sdk/docs/module-leancloud-realtime.html#.MessageStatus):
`MessageStatus.NONE` (unknown) `MessageStatus.SENDING` (sending) `MessageStatus.SENT` (sent) `MessageStatus.DELIVERED` (delivered) `MessageStatus.FAILED` (failed) |
-
->
-<>
-
-| Name | Type | Description |
-| -------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `content` | `IMMessage.Content` | The content of the message. Could be `String` or `Data`. |
-| `fromClientID` | `String` | The `clientId` of the sender. |
-| `currentClientID` | `String` | The `clientId` of the receiver. |
-| `conversationID` | `String` | The ID of the conversation. |
-| `ID` | `String` | A unique ID for each message. Assigned by the cloud automatically. |
-| `sentTimestamp` | `int64_t` | The time the message is sent. Assigned by the cloud automatically. |
-| `deliveredTimestamp` | `int64_t` | The time the message is received. |
-| `readTimestamp` | `int64_t` | The time the message is read. |
-| `patchedTimestamp` | `int64_t` | The time the message is edited. |
-| `isAllMembersMentioned` | `Bool` | Whether all members are mentioned. |
-| `mentionedMembers` | `[String]` | A list of members being mentioned. |
-| `isCurrentClientMentioned` | `Bool` | Whether the current `Client` is mentioned. |
-| `status` | `IMMessage.Status` | The status of the message. Could be one of:
`none` (unknown) `sending` (sending) `sent` (sent) `delivered` (delivered) `read` (read) `failed` (failed) |
-| `ioType` | `IMMessage.IOType` | The direction of the message. Could be one of:
`in` (sent to the current user) `out` (sent by the current user) |
-
->
-
-
-A number is assigned to each message type which can be used by your app to identify it. Negative numbers are for those defined by the SDK (see the table below) and positive ones are for your own types. `0` is reserved for untyped messages.
-
-| Message Type | Number |
-| ----------------- | ------ |
-| Text messages | `-1` |
-| Image messages | `-2` |
-| Audio messages | `-3` |
-| Video messages | `-4` |
-| Location messages | `-5` |
-| File messages | `-6` |
-
-### Image Messages
-
-#### Sending Image Files
-
-An image message can be constructed from either binary data or a local path. The diagram below shows the sequence of it:
-
->Local: 1. Get the content of the image
-Tom-->>Storage: 2. The SDK uploads the file (LCFile) to the cloud
-Storage-->>Tom: 3. Return the URL of the image
-Tom-->>Cloud: 4. The SDK sends the image message to the cloud
-Cloud->>Jerry: 5. Receive the image message and display it in UI
-`}
-/>
-
-Notes:
-
-1. The "Local" in the diagram could be `localStorage` or `camera`, meaning that the image could be either from the local storage of the phone (like iPhone's Photo Library) or taken in real time with the camera API.
-2. `LCFile` is the file object used by the Data Storage service.
-
-The diagram above may look complicated, but the code itself is quite simple since the image gets automatically uploaded when being sent with the `send` method:
-
-
-
-```cs
-var image = new LCFile("screenshot.png", new Uri("http://example.com/screenshot.png"));
-var imageMessage = new LCIMImageMessage(image);
-imageMessage.Text = "Sent from Windows";
-await conversation.Send(imageMessage);
-```
-
-```java
-LCFile file = LCFile.withAbsoluteLocalPath("San_Francisco.png", Environment.getExternalStorageDirectory() + "/San_Francisco.png");
-// Create an image message
-LCIMImageMessage m = new LCIMImageMessage(file);
-m.setText("Sent from Android");
-conv.sendMessage(m, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- if (e == null) {
- // Sent
- }
- }
-});
-```
-
-```objc
-NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
-NSString *documentsDirectory = [paths objectAtIndex:0];
-NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:@"Tarara.png"];
-NSError *error;
-LCFile *file = [LCFile fileWithLocalPath:imagePath error:&error];
-LCIMImageMessage *message = [LCIMImageMessage messageWithText:@"Tarara looks sweet." file:file attributes:nil];
-[conversation sendMessage:message callback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"Sent!");
- }
-}];
-```
-
-```js
-// ImageMessage and other rich media messages depend on the Data Storage service and the rich media message plugin.
-// Refer to the SDK setup guide for details on how to import and initialize the SDKs.
-
-var fileUploadControl = $("#photoFileUpload")[0];
-var file = new AV.File("avatar.jpg", fileUploadControl.files[0]);
-file
- .save()
- .then(function () {
- var message = new ImageMessage(file);
- message.setText("Sent from Ins");
- message.setAttributes({ location: "San Francisco" });
- return conversation.send(message);
- })
- .then(function () {
- console.log("Sent!");
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- if let imageFilePath = Bundle.main.url(forResource: "image", withExtension: "jpg")?.path {
- let imageMessage = IMImageMessage(filePath: imageFilePath, format: "jpg")
- try conversation.send(message: imageMessage, completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-import 'package:flutter/services.dart' show rootBundle;
-
-// Assuming there is an `assets` directory under the root directory of the project, and this directory is included in pubspec.yaml.
-ByteData imageData = await rootBundle.load('assets/test.png');
-// image message
-ImageMessage imageMessage = ImageMessage.from(
- binaryData: imageData.buffer.asUint8List(),
- format: 'png',
- name: 'image.png',
-);
-try {
- conversation.send(message: imageMessage);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-#### Sending Image URLs
-
-Besides sending an image directly, a user may also copy the URL of an image from somewhere else and send it to a conversation:
-
-
-
-```cs
-var image = new LCFile("girl.gif", new Uri("http://example.com/girl.gif"));
-var imageMessage = new LCIMImageMessage(image);
-imageMessage.Text = "Sent from Windows";
-await conversation.Send(imageMessage);
-```
-
-```java
-LCFile file = new LCFile("girl","http://ww3.sinaimg.cn/bmiddle/596b0666gw1ed70eavm5tg20bq06m7wi.gif", null);
-LCIMImageMessage m = new LCIMImageMessage(file);
-m.setText("She looks sweet.");
-// Create an image message
-conv.sendMessage(m, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- if (e == null) {
- // Sent
- }
- }
-});
-```
-
-```objc
-// Tom sends an image to Jerry
-LCFile *file = [LCFile fileWithURL:[self @"http://ww3.sinaimg.cn/bmiddle/596b0666gw1ed70eavm5tg20bq06m7wi.gif"]];
-LCIMImageMessage *message = [LCIMImageMessage messageWithText:@"girl" file:file attributes:nil];
-[conversation sendMessage:message callback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"Sent!");
- }
-}];
-```
-
-```js
-var AV = require("leancloud-storage");
-var { ImageMessage } = initPlugin(AV, IM);
-// Create an image message from URL
-var file = new AV.File.withURL(
- "girl",
- "http://pic2.zhimg.com/6c10e6053c739ed0ce676a0aff15cf1c.gif"
-);
-file
- .save()
- .then(function () {
- var message = new ImageMessage(file);
- message.setText("She looks sweet.");
- return conversation.send(message);
- })
- .then(function () {
- console.log("Sent!");
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- if let url = URL(string: "http://ww3.sinaimg.cn/bmiddle/596b0666gw1ed70eavm5tg20bq06m7wi.gif") {
- let imageMessage = IMImageMessage(url: url, format: "gif")
- try conversation.send(message: imageMessage, completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-ImageMessage imageMessage = ImageMessage.from(
- url: 'http://ww3.sinaimg.cn/bmiddle/596b0666gw1ed70eavm5tg20bq06m7wi.gif',
- format: 'png',
- name: 'image.png',
-);
-try {
- conversation.send(message: imageMessage);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-#### Receiving Image Messages
-
-The way to receive image messages is similar to that of basic messages. The only thing that needs to be added is to have the callback function retrieve the image and render it on the UI. For example:
-
-
-
-```cs
-client.OnMessage = (conv, msg) => {
- if (e.Message is LCIMImageMessage imageMessage) {
- WriteLine(imageMessage.Url);
- }
-}
-```
-
-```java
-LCIMMessageManager.registerMessageHandler(LCIMImageMessage.class,
- new LCIMTypedMessageHandler() {
- @Override
- public void onMessage(LCIMImageMessage msg, LCIMConversation conv, LCIMClient client) {
- // Only handle messages from Jerry
- // sent to the conversation with conversationId 55117292e4b065f7ee9edd29
- if ("Jerry".equals(client.getClientId()) && "55117292e4b065f7ee9edd29".equals(conv.getConversationId())) {
- String fromClientId = msg.getFrom();
- String messageId = msg.getMessageId();
- String url = msg.getFileUrl();
- Map metaData = msg.getFileMetaData();
- if (metaData.containsKey("size")) {
- int size = (Integer) metaData.get("size");
- }
- if (metaData.containsKey("width")) {
- int width = (Integer) metaData.get("width");
- }
- if (metaData.containsKey("height")) {
- int height = (Integer) metaData.get("height");
- }
- if (metaData.containsKey("format")) {
- String format = (String) metaData.get("format");
- }
- }
- }
-});
-```
-
-```objc
-- (void)conversation:(LCIMConversation *)conversation didReceiveTypedMessage:(LCIMTypedMessage *)message {
- LCIMImageMessage *imageMessage = (LCIMImageMessage *)message;
-
- // The ID of the message
- NSString *messageId = imageMessage.messageId;
- // The URL of the image file
- NSString *imageUrl = imageMessage.file.url;
- // The clientId of the sender
- NSString *fromClientId = message.clientId;
-}
-```
-
-```js
-var { Event, TextMessage } = require('leancloud-realtime');
-var { ImageMessage } = initPlugin(AV, IM);
-
-client.on(Event.MESSAGE, function messageEventHandler(message, conversation) {
- var file;
- switch (message.type) {
- case ImageMessage.TYPE:
- file = message.getFile();
- console.log('Image received. URL: ' + file.url());
- break;
- }
-}
-```
-
-```swift
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case .message(event: let messageEvent):
- switch messageEvent {
- case .received(message: let message):
- switch message {
- case let imageMessage as IMImageMessage:
- print(imageMessage)
- default:
- break
- }
- default:
- break
- }
- default:
- break
- }
-}
-```
-
-```dart
-lient.onMessage = ({
- Client client,
- Conversation conversation,
- Message message,
-}) {
- if (message is ImageMessage) {
- print('Image received. URL: ${message.url}');
- }
-};
-```
-
-
-
-### Sending Audios, Videos, and Files
-
-#### The Workflow
-
-The SDK follows the steps below to send images, audios, videos, and files:
-
-When **constructing a file from a data stream using the client API**:
-
-1. Construct an `LCFile` locally
-2. Upload the `LCFile` to the cloud and retrieve its `metaData`
-3. Embed the `objectId`, URL, and metadata of the `LCFile` into the message
-4. Send the message
-
-When **constructing a file with an external URL**:
-
-1. Embed the URL into the message without the metadata (like the length of audio) or `objectId`
-2. Send the message
-
-For example, when sending an audio message, the basic workflow would be: read the audio file (or record a new one) > construct an audio message > send the message.
-
-
-
-```cs
-var audio = new LCFile("never-gonna-give-you-up.mp3", Path.Combine(Application.persistentDataPath, "never-gonna-give-you-up.mp3"));
-var audioMessage = new LCIMAudioMessage(audio);
-audioMessage.Text = "Check this out!";
-await conversation.Send(audioMessage);
-```
-
-```java
-LCFile file = LCFile.withAbsoluteLocalPath("never-gonna-give-you-up.mp3",localFilePath);
-LCIMAudioMessage m = new LCIMAudioMessage(file);
-m.setText("Check this out!");
-// Create an audio message
-conv.sendMessage(m, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- if (e == null) {
- // Sent
- }
- }
-});
-```
-
-```objc
-NSError *error = nil;
-LCFile *file = [LCFile fileWithLocalPath:localPath error:&error];
-if (!error) {
- LCIMAudioMessage *message = [LCIMAudioMessage messageWithText:@"Check this out!" file:file attributes:nil];
- [conversation sendMessage:message callback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"Sent!");
- }
- }];
-}
-```
-
-```js
-var AV = require("leancloud-storage");
-var { AudioMessage } = initPlugin(AV, IM);
-
-var fileUploadControl = $("#musicFileUpload")[0];
-var file = new AV.File(
- "never-gonna-give-you-up.mp3",
- fileUploadControl.files[0]
-);
-file
- .save()
- .then(function () {
- var message = new AudioMessage(file);
- message.setText("Check this out!");
- return conversation.send(message);
- })
- .then(function () {
- console.log("Sent!");
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- if let filePath = Bundle.main.url(forResource: "audio", withExtension: "mp3")?.path {
- let audioMessage = IMAudioMessage(filePath: filePath, format: "mp3")
- audioMessage.text = "Check this out!"
- try conversation.send(message: audioMessage, completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-import 'package:flutter/services.dart' show rootBundle;
-
-// Assuming there is an `assets` directory under the root directory of the project containing an mp3 file, and this directory is included in pubspec.yaml.
-ByteData audioData = await rootBundle.load('assets/test.mp3');
-AudioMessage audioMessage = AudioMessage.from(
- binaryData: audioData.buffer.asUint8List(),
- format: 'mp3',
-);
-audioMessage.text = 'Check this out!';
-try {
- await conversation.send(message: audioMessage);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-Similar to image messages, you can construct audio messages from URLs as well:
-
-
-
-```cs
-var audio = new LCFile("apple.aac", new Uri("https://some.website.com/apple.aac"));
-var audioMessage = new LCIMAudioMessage(audio);
-audioMessage.Text = "Here is the recording from Apple Special Event.";
-await conversation.Send(audioMessage);
-```
-
-```java
-LCFile file = new LCFile("apple.aac", "https://some.website.com/apple.aac", null);
-LCIMAudioMessage m = new LCIMAudioMessage(file);
-m.setText("Here is the recording from Apple Special Event.");
-conv.sendMessage(m, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- if (e == null) {
- // Sent
- }
- }
-});
-```
-
-```objc
-LCFile *file = [LCFile fileWithRemoteURL:[NSURL URLWithString:@"https://some.website.com/apple.aac"]];
-LCIMAudioMessage *message = [LCIMAudioMessage messageWithText:@"Here is the recording from Apple Special Event." file:file attributes:nil];
-[conversation sendMessage:message callback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"Sent!");
- }
-}];
-```
-
-```js
-var AV = require("leancloud-storage");
-var { AudioMessage } = initPlugin(AV, IM);
-
-var file = new AV.File.withURL(
- "apple.aac",
- "https://some.website.com/apple.aac"
-);
-file
- .save()
- .then(function () {
- var message = new AudioMessage(file);
- message.setText("Here is the recording from Apple Special Event.");
- return conversation.send(message);
- })
- .then(function () {
- console.log("Sent!");
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- if let url = URL(string: "https://some.website.com/apple.aac") {
- let audioMessage = IMAudioMessage(url: url, format: "aac")
- audioMessage.text = "Here is the recording from Apple Special Event."
- try conversation.send(message: audioMessage, completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-AudioMessage audioMessage = AudioMessage.from(
- url: 'https://some.website.com/apple.aac',
- name: 'apple.aac',
-);
-try {
- await conversation.send(message: audioMessage);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-### Sending Location Messages
-
-The code below sends a message containing a location:
-
-
-
-```cs
-var location = new LCGeoPoint(31.3753285, 120.9664658);
-var locationMessage = new LCIMLocationMessage(location);
-await conversation.Send(locationMessage);
-```
-
-```java
-final LCIMLocationMessage locationMessage = new LCIMLocationMessage();
-// The location here is hardcoded for demonstration; you can get actual locations with the API offered by the device
-locationMessage.setLocation(new LCGeoPoint(31.3753285,120.9664658));
-locationMessage.setText("Here is the location of the bakery.");
-conversation.sendMessage(locationMessage, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- if (null != e) {
- e.printStackTrace();
- } else {
- // Sent
- }
- }
-});
-```
-
-```objc
-LCIMLocationMessage *message = [LCIMLocationMessage messageWithText:@"Here is the location of the bakery." latitude:31.3753285 longitude:120.9664658 attributes:nil];
-[conversation sendMessage:message callback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"Sent!");
- }
-}];
-```
-
-```js
-var AV = require("leancloud-storage");
-var { LocationMessage } = initPlugin(AV, IM);
-
-var location = new AV.GeoPoint(31.3753285, 120.9664658);
-var message = new LocationMessage(location);
-message.setText("Here is the location of the bakery.");
-conversation
- .send(message)
- .then(function () {
- console.log("Sent!");
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- let locationMessage = IMLocationMessage(latitude: 31.3753285, longitude: 120.9664658)
- try conversation.send(message: locationMessage, completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-LocationMessage locationMessage = LocationMessage.from(
- latitude: 22,
- longitude: 33,
-);
-try {
- await conversation.send(message: locationMessage);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-### Back to Receiving Messages
-
-
-<>
-
-The C# SDK handles new messages with `OnMessage` event callbacks:
-
-```cs
-jerry.OnMessage = (conv, msg) => {
- if (msg is LCIMImageMessage imageMessage) {
-
- } else if (msg is LCIMAudioMessage audioMessage) {
-
- } else if (msg is LCIMVideoMessage videoMessage) {
-
- } else if (msg is LCIMFileMessage fileMessage) {
-
- } else if (msg is AVIMLocationMessage locationMessage) {
-
- } else if (msg is InputtingMessage) {
- WriteLine($"Custom message received: {inputtingMessage.TextContent} {inputtingMessage.Ecode}");
- }
-}
-```
-
->
-<>
-
-The Java/Android SDK handles new messages with `LCIMMessageHandler`. You can register your own message handlers by calling `LCIMMessageManager.registerDefaultMessageHandler`. `LCIMMessageManager` offers two different methods for you to register default message handlers and handlers for specific message types:
-
-```java
-/**
- * Register default message handler.
- *
- * @param handler
- */
-public static void registerDefaultMessageHandler(LCIMMessageHandler handler);
-/**
- * Register handler for specific message type.
- *
- * @param clazz The message type
- * @param handler
- */
-public static void registerMessageHandler(Class extends LCIMMessage> clazz, MessageHandler> handler);
-/**
- * Deregister handler for specific message type.
- *
- * @param clazz
- * @param handler
- */
-public static void unregisterMessageHandler(Class extends LCIMMessage> clazz, MessageHandler> handler);
-```
-
-Different handlers can be registered or deregistered for different message types (including those defined by yourself). These handlers should be set up when initializing the app.
-
-If you call `registerDefaultMessageHandler` on `LCIMMessageManager` multiple times, only the last one would take effect. However, if you register `LCIMMessageHandler` through `registerMessageHandler`, different handlers could coexist with each other.
-
-When a message is received by the client, the SDK would:
-
-- Detect the type of the message, look for all the handlers registered for this type, and call the `onMessage` functions within all these handlers.
-- If no handler is found for this type, `defaultHandler` will be triggered.
-
-So when handlers are specified for `AVIMTypedMessage` (and its subtypes) and a global `defaultHandler` is also specified, if the sender sends a general `LCIMMessage` message, the receiver will have its handler in `LCIMMessageManager.registerDefaultMessageHandler()` triggered; if the sender sends a message of `LCIMTypedMessage` (or its subtype), the receiver will have its handler in `LCIMMessageManager#registerMessageHandler()` triggered.
-
-```java
-// 1. Register the default handler, which will only be invoked when none of the other handlers are invoked
-LCIMMessageManager.registerDefaultMessageHandler(new LCIMMessageHandler(){
- public void onMessage(LCIMMessage message, LCIMConversation conversation, LCIMClient client) {
- // Receive the message
- }
-
- public void onMessageReceipt(LCIMMessage message, LCIMConversation conversation, LCIMClient client) {
- // Your application may add new custom message types in the future. The SDK may add new built-in message types as well.
- // Therefore, do not forget to handle them here. For example, you can notify the user to upgrade to a new version.
- }
-});
-// 2. Register a handler for each type of message
-LCIMMessageManager.registerMessageHandler(LCIMTypedMessage.class, new LCIMTypedMessageHandler(){
- public void onMessage(LCIMTypedMessage message, LCIMConversation conversation, LCIMClient client) {
- switch (message.getMessageType()) {
- case LCIMMessageType.TEXT_MESSAGE_TYPE:
- // Do something
- LCIMTextMessage textMessage = (LCIMTextMessage)message;
- break;
- case LCIMMessageType.IMAGE_MESSAGE_TYPE:
- // Do something
- LCIMImageMessage imageMessage = (LCIMImageMessage)message;
- break;
- case LCIMMessageType.AUDIO_MESSAGE_TYPE:
- // Do something
- LCIMAudioMessage audioMessage = (LCIMAudioMessage)message;
- break;
- case LCIMMessageType.VIDEO_MESSAGE_TYPE:
- // Do something
- LCIMVideoMessage videoMessage = (LCIMVideoMessage)message;
- break;
- case LCIMMessageType.LOCATION_MESSAGE_TYPE:
- // Do something
- LCIMLocationMessage locationMessage = (LCIMLocationMessage)message;
- break;
- case LCIMMessageType.FILE_MESSAGE_TYPE:
- // Do something
- LCIMFileMessage fileMessage = (LCIMFileMessage)message;
- break;
- case LCIMMessageType.RECALLED_MESSAGE_TYPE:
- // Do something
- LCIMRecalledMessage recalledMessage = (LCIMRecalledMessage)message;
- break;
- case 123:
- // This is a custom message type
- // Do something
- CustomMessage customMessage = (CustomMessage)message;
- break;
- }
- }
-
- public void onMessageReceipt(LCIMTypedMessage message, LCIMConversation conversation, LCIMClient client) {
- // Do something after receiving the message
- }
-});
-```
-
->
-<>
-
-The Objective-C SDK handles new messages with `LCIMClientDelegate` and uses two separate methods to handle basic messages (`LCIMMessage`) and rich media messages (`LCIMTypedMessage`; including messages with custom types):
-
-```objc
-/*!
- New basic message received.
- @param conversation - The conversation.
- @param message - The content of the message.
- */
-- (void)conversation:(LCIMConversation *)conversation didReceiveCommonMessage:(LCIMMessage *)message;
-
-/*!
- New rich media message received.
- @param conversation - The conversation.
- @param message - The content of the message.
- */
-- (void)conversation:(LCIMConversation *)conversation didReceiveTypedMessage:(LCIMTypedMessage *)message;
-```
-
-```objc
-// Handle messages with built-in types
-- (void)conversation:(LCIMConversation *)conversation didReceiveTypedMessage:(LCIMTypedMessage *)message {
- if (message.mediaType == LCIMMessageMediaTypeImage) {
- LCIMImageMessage *imageMessage = (LCIMImageMessage *)message; // Handle image message
- } else if(message.mediaType == LCIMMessageMediaTypeAudio){
- // Handle audio message
- } else if(message.mediaType == LCIMMessageMediaTypeVideo){
- // Handle video message
- } else if(message.mediaType == LCIMMessageMediaTypeLocation){
- // Handle location message
- } else if(message.mediaType == LCIMMessageMediaTypeFile){
- // Handle file message
- } else if(message.mediaType == LCIMMessageMediaTypeText){
- // Handle text message
- } else if(message.mediaType == 123){
- // Handle custom message type
- }
-}
-
-// Handle unknown messages types
-- (void)conversation:(LCIMConversation *)conversation didReceiveCommonMessage:(LCIMMessage *)message {
- // Your application may add new custom message types in the future. The SDK may add new built-in message types as well.
- // Therefore, do not forget to handle them here. For example, you can notify the user to upgrade to a new version.
-}
-```
-
->
-<>
-
-When a new message comes in, the JavaScript SDK would always trigger the callback set for the event `Event.MESSAGE` on `IMClient` regardless of the type of the message. You can address different types of messages in different ways within the callback function.
-
-```js
-// Load TypedMessagesPlugin when initializing Realtime
-var { Event, TextMessage } = require("leancloud-realtime");
-var { FileMessage, ImageMessage, AudioMessage, VideoMessage, LocationMessage } =
- initPlugin(AV, IM);
-// Register handler for the MESSAGE event
-client.on(Event.MESSAGE, function messageEventHandler(message, conversation) {
- // Your logic here
- var file;
- switch (message.type) {
- case TextMessage.TYPE:
- console.log(
- "Text message received. Text: " +
- message.getText() +
- ", ID: " +
- message.id
- );
- break;
- case FileMessage.TYPE:
- file = message.getFile(); // file is an AV.File instance
- console.log(
- "File message received. URL: " +
- file.url() +
- ", Size: " +
- file.metaData("size")
- );
- break;
- case ImageMessage.TYPE:
- file = message.getFile();
- console.log(
- "Image message received. URL: " +
- file.url() +
- ", Width: " +
- file.metaData("width")
- );
- break;
- case AudioMessage.TYPE:
- file = message.getFile();
- console.log(
- "Audio message received. URL: " +
- file.url() +
- ", Duration: " +
- file.metaData("duration")
- );
- break;
- case VideoMessage.TYPE:
- file = message.getFile();
- console.log(
- "Video message received. URL: " +
- file.url() +
- ", Duration: " +
- file.metaData("duration")
- );
- break;
- case LocationMessage.TYPE:
- var location = message.getLocation();
- console.log(
- "Location message received. Latitude: " +
- location.latitude +
- ", Longitude: " +
- location.longitude
- );
- break;
- case 1:
- console.log("OperationMessage is the custom message type");
- default:
- // Your application may add new custom message types in the future. The SDK may add new built-in message types as well.
- // Therefore, do not forget to handle them here. For example, you can notify the user to upgrade to a new version.
- console.warn("收到未知类型消息");
- }
-});
-
-// `MESSAGE` event will be triggered on conversation as well
-conversation.on(Event.MESSAGE, function messageEventHandler(message) {
- // Your logic here
-});
-```
-
->
-<>
-
-The Swift SDK handles new messages with `IMClientDelegate`:
-
-```swift
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case .message(event: let messageEvent):
- switch messageEvent {
- case .received(message: let message):
- print(message)
- default:
- break
- }
- default:
- break
- }
-}
-```
-
-```swift
-// Handle messages with built-in types
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case .message(event: let messageEvent):
- switch messageEvent {
- case .received(message: let message):
- if let categorizedMessage = message as? IMCategorizedMessage {
- switch categorizedMessage {
- case let textMessage as IMTextMessage:
- print(textMessage)
- case let imageMessage as IMImageMessage:
- print(imageMessage)
- case let audioMessage as IMAudioMessage:
- print(audioMessage)
- case let videoMessage as IMVideoMessage:
- print(videoMessage)
- case let fileMessage as IMFileMessage:
- print(fileMessage)
- case let locationMessage as IMLocationMessage:
- print(locationMessage)
- case let recalledMessage as IMRecalledMessage:
- print(recalledMessage)
- case let customMessage as CustomMessage:
- print("customMessage is a custom message type")
- default:
- break
- } else {
- // Your application may add new custom message types in the future. The SDK may add new built-in message types as well.
- // Therefore, do not forget to handle them here. For example, you can notify the user to upgrade to a new version.
- print("Message with unknown type received.")
- }
- default:
- break
- }
- default:
- break
- }
-}
-```
-
->
-<>
-
-```dart
-jerry.onMessage = ({
- Client client,
- Conversation conversation,
- Message message,
-}) {
- if (message.binaryContent != null) {
- print('Received a binary message: ${message.binaryContent.toString()}');
- } else if (message is TextMessage) {
- print('Received a text message: ${message.text}');
- } else if (message is LocationMessage) {
- print('Received a location message: ${message.latitude},${message.longitude}');
- } else if (message is FileMessage) {
- if (message is ImageMessage) {
- print('Received an image message: ${message.url}');
- } else if (message is AudioMessage) {
- print('Received an audio message: ${message.duration}');
- } else if (message is VideoMessage) {
- print('Received a video message: ${message.duration}');
- } else {
- print('Received a file message: ${message.url}');
- }
- } else if (message is CustomMessage) {
- // CustomMessage is a custom message type
- print('Received a custom message');
- } else {
- // Add more conditions for custom types
- print('Received an unknown message');
- if (message.stringContent != null) {
- print('Received a basic message: ${message.stringContent}');
- }
- }
-};
-```
-
->
-
-
-The code above involves the reception of messages with custom types.
-We will cover more about it in [the second chapter](/sdk/im/guide/intermediate/).
-
-## Custom Attributes
-
-A `Conversation` object holds some built-in properties which match the fields in the `_Conversation` table. The table below shows these **built-in** properties:
-
-
-
-| Property of `AVIMConversation` | Field in `_Conversation` | Description |
-| ------------------------------ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `CurrentClient` | N/A | The `AVIMClient` the conversation belongs to. |
-| `ConversationId` | `objectId` | A globally unique ID. |
-| `Name` | `name` | The name of the conversation. Shared by all members. |
-| `MemberIds` | `m` | The list of members. |
-| `MuteMemberIds` | `mu` | The list of members that muted the conversation. |
-| `Creator` | `c` | The creator of the conversation. |
-| `IsTransient` | `tr` | Whether it is a chat room. |
-| `IsSystem` | `sys` | Whether it is a system conversation. |
-| `IsUnique` | `unique` | If this is `true`, the same conversation will be reused when a new conversation is created with the same composition of members and `unique` to be `true`. |
-| `IsTemporary` | N/A | Whether it is a temporary conversation that will not be saved to the `_Conversation` class. |
-| `CreatedAt` | `createdAt` | The time the conversation is created. |
-| `UpdatedAt` | `updatedAt` | The time the conversation is updated. |
-| `LastMessageAt` | `lm` | The time the last message is sent. |
-
-| Getter of `LCIMConversation` | Field in `_Conversation` | Description |
-| ---------------------------- | ------------------------ | ------------------------------------------------------------------------------------------- |
-| `getAttributes` | `attr` | Custom attributes. |
-| `getConversationId` | `objectId` | A globally unique ID. |
-| `getCreatedAt` | `createdAt` | The time the conversation is created. |
-| `getCreator` | `c` | The creator of the conversation. |
-| `getLastDeliveredAt` | N/A | The time the last message being delivered is sent (for one-on-one chats only). |
-| `getLastMessage` | N/A | The last message. Could be empty. |
-| `getLastMessageAt` | `lm` | The time the last message is sent. |
-| `getLastReadAt` | N/A | The time the last message being read is sent (for one-on-one chats only). |
-| `getMembers` | `m` | The list of members. |
-| `getName` | `name` | The name of the conversation. Shared by all members. |
-| `getTemporaryExpiredat` | N/A | Time to live (applicable for temporary conversations only). |
-| `getUniqueId` | `uniqueId` | A globally unique `ID` for `Unique Conversation`. |
-| `getUnreadMessagesCount` | N/A | The number of unread messages. |
-| `getUpdatedAt` | `updatedAt` | The time the conversation is updated. |
-| `isSystem` | `sys` | Whether it is a system conversation. |
-| `isTemporary` | N/A | Whether it is a temporary conversation that will not be saved to the `_Conversation` class. |
-| `isTransient` | `tr` | Whether it is a chat room. |
-| `isUnique` | `unique` | Whether it is a `Unique Conversation`. |
-| `unreadMessagesMentioned` | N/A | Whether unread messages contain a mention of the current `Client`. |
-
-| Property of `LCIMConversation` | Field in `_Conversation` | Description |
-| ------------------------------ | ------------------------ | ------------------------------------------------------------------------------------------- |
-| `clientID` | N/A | The `ID` of the `Client` the conversation belongs to. |
-| `conversationId` | `objectId` | A globally unique ID. |
-| `creator` | `c` | The creator of the conversation. |
-| `createdAt` | `createdAt` | The time the conversation is created. |
-| `updatedAt` | `updatedAt` | The time the conversation is updated. |
-| `lastMessage` | N/A | The last message. Could be empty. |
-| `lastMessageAt` | `lm` | The time the last message is sent. |
-| `lastReadAt` | N/A | The time the last message being read is sent (for one-on-one chats only). |
-| `lastDeliveredAt` | N/A | The time the last message being delivered is sent (for one-on-one chats only). |
-| `unreadMessagesCount` | N/A | The number of unread messages. |
-| `unreadMessageContainMention` | N/A | Whether unread messages contain a mention of the current `Client`. |
-| `name` | `name` | The name of the conversation. Shared by all members. |
-| `members` | `m` | The list of members. |
-| `attributes` | `attr` | Custom attributes. |
-| `uniqueId` | `uniqueId` | A globally unique `ID` for `Unique Conversation`. |
-| `unique` | `unique` | Whether it is a `Unique Conversation`. |
-| `transient` | `tr` | Whether it is a chat room. |
-| `system` | `sys` | Whether it is a system conversation. |
-| `temporary` | N/A | Whether it is a temporary conversation that will not be saved to the `_Conversation` class. |
-| `temporaryTTL` | N/A | Time to live (applicable for temporary conversations only). |
-| `muted` | N/A | Whether the current user muted the conversation. |
-| `imClient` | N/A | The `LCIMClient` the conversation belongs to. |
-
-| Property of `Conversation` | Field in `_Conversation` | Description |
-| -------------------------- | ------------------------ | ------------------------------------------------------------------------------ |
-| `createdAt` | `createdAt` | The time the conversation is created. |
-| `creator` | `c` | The creator of the conversation. |
-| `id` | `objectId` | A globally unique ID. |
-| `lastDeliveredAt` | N/A | The time the last message being delivered is sent (for one-on-one chats only). |
-| `lastMessage` | N/A | The last message. Could be empty. |
-| `lastMessageAt` | `lm` | The time the last message is sent. |
-| `lastReadAt` | N/A | The time the last message being read is sent (for one-on-one chats only). |
-| `members` | `m` | The list of members. |
-| `muted` | N/A | Whether the current user muted the conversation. |
-| `mutedMembers` | `mu` | The list of members that muted the conversation. |
-| `name` | `name` | The name of the conversation. Shared by all members. |
-| `system` | `sys` | Whether it is a system conversation. |
-| `transient` | `tr` | Whether it is a chat room. |
-| `unreadMessagesCount` | N/A | The number of unread messages. |
-| `updatedAt` | `updatedAt` | The time the conversation is updated. |
-
-| Property of `IMConversation` | Field in `_Conversation` | Description |
-| ------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
-| `client` | N/A | The `Client` the conversation belongs to. |
-| `ID` | `objectId` | A globally unique `ID`. |
-| `clientID` | N/A | The `ID` of the `Client` the conversation belongs to. |
-| `isUnique` | `unique` | Whether it is a `Unique Conversation`. |
-| `uniqueID` | `uniqueId` | A globally unique `ID` for `Unique Conversation`. |
-| `name` | `name` | The name of the conversation. |
-| `creator` | `c` | The creator of the conversation. |
-| `createdAt` | `createdAt` | The time the conversation is created. |
-| `updatedAt` | `updatedAt` | The time the conversation is updated. |
-| `attributes` | `attr` | Custom attributes. |
-| `members` | `m` | The list of members. |
-| `isMuted` | N/A | Whether the current user muted the conversation. |
-| `isOutdated` | N/A | Whether the properties of the conversation are outdated. Can be used to determine if the data of the conversation needs to be updated. |
-| `lastMessage` | N/A | The last message. Could be empty. |
-| `unreadMessageCount` | N/A | The number of unread messages. |
-| `isUnreadMessageContainMention` | N/A | Whether unread messages contain a mention of the current `Client`. |
-| `memberInfoTable` | N/A | A table of member information. |
-
-| `Conversation` 属性名 | `_Conversation` 字段 | 含义 |
-| ------------------------- | -------------------- | ------------------------------------------------------------------------------ |
-| `attributes` | `attr` | Custom attributes. |
-| `client` | N/A | The `Client` the conversation belongs to. |
-| `createdAt` | `createdAt` | The time the conversation is created. |
-| `creator` | `c` | The creator of the conversation. |
-| `id` | `objectId` | A globally unique `ID`. |
-| `isMuted` | N/A | Whether the current user muted the conversation. |
-| `isUnique` | `unique` | Whether it is a `Unique Conversation`. |
-| `lastDeliveredAt` | N/A | The time the last message being delivered is sent (for one-on-one chats only). |
-| `lastMessage` | N/A | The last message. Could be empty. |
-| `lastReadAt` | N/A | The time the last message being read is sent (for one-on-one chats only). |
-| `members` | `m` | The list of members. |
-| `name` | `name` | The name of the conversation. Shared by all members. |
-| `uniqueID` | `uniqueId` | A globally unique `ID` for `Unique Conversation`. |
-| `unreadMessagesCount` | N/A | The number of unread messages. |
-| `unreadMessagesMentioned` | N/A | Whether unread messages contain a mention of the current `Client`. |
-| `updatedAt` | `updatedAt` | The time the conversation is updated. |
-
-
-
-However, direct write operations on the `_Conversation` table are frowned upon:
-
-- The conversation queries sent by client-side SDKs in websocket connections will first reach the Instant Messaging server's in-memory cache. Direct write operations on the `_Conversation` table will not update the cache, which may cause cache inconsistency.
-- With direct write operations on the `_Conversation` table, the Instant Messaging server has no chance to notify the client-side. Thus the client-side will not receive any corresponding events.
-- If Instant Messaging hooks are defined, direct write operations on the `_Conversation` table will not trigger them.
-
-For administrative tasks, the dedicated Instant Messaging REST API interface is recommended.
-
-Besides these built-in properties, you can also define your custom attributes to store more data with each conversation.
-
-### Creating Custom Attributes
-
-When introducing [one-on-one conversations](#creating-conversations), we mentioned that `IMClient#createConversation` allows you to attach custom attributes to a conversation. Now let's see how we can do that.
-
-Assume that we need to add two properties `{ "type": "private", "pinned": true }` to a conversation we are creating. We can do so by passing in the properties when calling `IMClient#createConversation`:
-
-
-
-```cs
-var properties = new Dictionary {
- { "type", "private" },
- { "pinned", true }
-};
-var conversation = await tom.CreateConversation("Jerry", name: "Tom & Jerry", unique: true, properties: properties);
-```
-
-```java
-HashMap attr = new HashMap();
-attr.put("type","private");
-attr.put("pinned",true);
-client.createConversation(Arrays.asList("Jerry"),"Tom & Jerry", attr, false, true,
- new LCIMConversationCreatedCallback(){
- @Override
- public void done(LCIMConversation conv,LCIMException e){
- if(e==null){
- // Conversation created
- }
- }
- });
-```
-
-```objc
-// Tom creates a conversation named "Tom & Jerry" and attaches custom attributes to it
-LCIMConversationCreationOption *option = [LCIMConversationCreationOption new];
-option.name = @"Tom & Jerry";
-option.attributes = @{
- @"type": @"private",
- @"pinned": @(YES)
-};
-[self createConversationWithClientIds:@[@"Jerry"] option:option callback:^(LCIMConversation * _Nullable conversation, NSError * _Nullable error) {
- if (succeeded) {
- NSLog(@"Conversation created!");
- }
-}];
-```
-
-```js
-tom
- .createConversation({
- members: ["Jerry"],
- name: "Tom & Jerry",
- unique: true,
- type: "private",
- pinned: true,
- })
- .then(function (conversation) {
- console.log("Conversation created! ID: " + conversation.id);
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- try tom.createConversation(clientIDs: ["Jerry"], name: "Tom & Jerry", attributes: ["type": "private", "pinned": true], isUnique: true, completion: { (result) in
- switch result {
- case .success(value: let conversation):
- print(conversation)
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- Conversation conversation = await jerry.createConversation(
- members: {'client1.id', 'client2.id'},
- attributes: {
- 'members': ['Jerry'],
- 'name': 'Tom & Jerry',
- 'unique': true,
- 'type': 'private',
- 'pinned': true,
- },
- );
-} catch (e) {
- print(e);
-}
-```
-
-
-
-**The SDK allows everyone in a conversation to access its custom attributes.** You can even query conversations that satisfy certain custom attributes. See [Querying Conversations with Custom Conditions](#querying-conversations-with-custom-conditions).
-
-### Updating and Retrieving Properties
-
-The built-in properties (like `name`) of a `Conversation` object can be updated by all the members unless you set restrictions in your app:
-
-
-
-```cs
-await conversation.UpdateInfo(new Dictionary {
- { "name", "Tom is Smart" }
-});
-```
-
-```java
-LCIMConversation conversation = client.getConversation("55117292e4b065f7ee9edd29");
-conversation.setName("Tom is Smart");
-conversation.updateInfoInBackground(new LCIMConversationCallback(){
- @Override
- public void done(LCIMException e){
- if(e==null){
- // Updated
- }
- }
-});
-```
-
-```objc
-conversation[@"name"] = @"Tom is Smart";
-[conversation updateWithCallback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- NSLog(@"Updated!");
- }
-}];
-```
-
-```js
-conversation.name = "Tom is Smart";
-conversation.save();
-```
-
-```swift
-do {
- try conversation.update(attribution: ["name": "Tom is Smart"], completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- await conversation.updateInfo(attributes: {
- 'name': 'Tom is Smart',
- });
-} catch (e) {
- print(e);
-}
-```
-
-
-
-Custom attributes can also be retrieved or updated by all the members:
-
-
-
-```cs
-// Retrieve custom attribute
-var type = conversation["type"];
-// Set new value for pinned
-await conversation.UpdateInfo(new Dictionary {
- { "pinned", false }
-});
-```
-
-```java
-// Retrieve custom attribute
-String type = conversation.get("attr.type");
-// Set new value for pinned
-conversation.set("attr.pinned",false);
-// Save
-conversation.updateInfoInBackground(new LCIMConversationCallback(){
- @Override
- public void done(LCIMException e){
- if(e==null){
- // Saved
- }
- }
-});
-```
-
-```objc
-// Retrieve custom attribute
-NSString *type = conversation.attributes[@"type"];
-// Set new value for pinned
-[conversation setObject:@(NO) forKey:@"attr.pinned"];
-// Save
-[conversation updateWithCallback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"Saved!");
- }
-}];
-```
-
-```js
-// Retrieve custom attribute
-var type = conversation.get("attr.type");
-// Set new value for pinned
-conversation.set("attr.pinned", false);
-// Save
-conversation.save();
-```
-
-```swift
-do {
- let type = conversation.attributes?["type"] as? String
- try conversation.update(attribution: ["attr.pinned": false]) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
-// Retrieve custom attribute
- String type = conversation.attributes['type'];
-// Set new value for pinned
- await conversation.updateInfo(attributes: {
- 'pinned': false,
- });
-} catch (e) {
- print(e);
-}
-```
-
-
-
-Notes on custom attributes:
-
-The custom attributes specified with `IMClient#createConversation` will be stored in the field `attr` of the `_Conversation` table. If you need to retrieve or update them later, the full path needs to be specified, like `attr.type`.
-
-### Synchronization of Properties
-
-The properties of a conversation (like name) are shared by everyone in it. If someone ever changes a property, other members need to get updated on it. In the example we used earlier, a user changed the name of a conversation to "Tom is Smart". How would other members get to know about it?
-
-Instant Messaging offers the mechanism that automatically delivers the change made by a user to a conversation to all the members in it (for those who are offline, they will receive updates once they get online):
-
-
-
-```cs
-jerry.OnConversationInfoUpdated = (conv, attrs, initBy) => {
- WriteLine($"Conversation ${conv.Id} updated.");
-};
-```
-
-```java
-// The following definition exists in LCIMConversationEventHandler
-/**
- * The properties of a conversation are updated
- *
- * @param client
- * @param conversation
- * @param attr The properties being updated
- * @param operator The ID of the operator
- */
-public void onInfoChanged(LCIMClient client, LCIMConversation conversation, JSONObject attr,
- String operator)
-```
-
-```objc
-/// Notification for conversation's attribution updated.
-/// @param conversation Updated conversation.
-/// @param date Updated date.
-/// @param clientId Client ID which do this update.
-/// @param updatedData Updated data.
-/// @param updatingData Updating data.
-- (void)conversation:(LCIMConversation *)conversation didUpdateAt:(NSDate * _Nullable)date byClientId:(NSString * _Nullable)clientId updatedData:(NSDictionary * _Nullable)updatedData updatingData:(NSDictionary * _Nullable)updatingData;
-```
-
-```js
-/**
- * The properties of a conversation are updated
- * @event IMClient#CONVERSATION_INFO_UPDATED
- * @param {Object} payload
- * @param {Object} payload.attributes The properties being updated
- * @param {String} payload.updatedBy The ID of the operator
- */
-var { Event } = require("leancloud-realtime");
-client.on(Event.CONVERSATION_INFO_UPDATED, function (payload) {});
-```
-
-```swift
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case let .dataUpdated(updatingData: updatingData, updatedData: updatedData, byClientID: byClientID, at: atDate):
- print(updatingData)
- print(updatedData)
- print(byClientID)
- print(atDate)
- default:
- break
- }
-}
-```
-
-```dart
-jerry.onInfoUpdated = ({
- Client client,
- Conversation conversation,
- Map updatingAttributes,
- Map updatedAttributes,
- String byClientID,
- DateTime atDate,
-}) {
- print('Conversation ${conversation.id} updated.');
-};
-```
-
-
-
-Notes:
-
-You can either retrieve the properties being updated from the callback function or directly read the latest values from the `Conversation` object.
-
-### Retrieving Member Lists
-
-To get the list of members in a conversation, we can call the method for fetching on a `Conversation` object and then get the result from it:
-
-
-
-```cs
-await conversation.Fetch();
-```
-
-```java
-// fetchInfoInBackground will trigger an operation to retrieve the latest data from the cloud
-conversation.fetchInfoInBackground(new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- if (e == null) {
- conversation.getMembers();
- }
- }
-});
-```
-
-```objc
-// fetchWithCallback will trigger an operation to retrieve the latest data from the cloud
-[conversation fetchWithCallback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"", conversation.members);
- }
-}];
-```
-
-```js
-// fetch will trigger an operation to retrieve the latest data from the cloud
-conversation.fetch().then(function(conversation) {
- console.log('members: ', conversation.members);
-).catch(console.error.bind(console));
-```
-
-```swift
-do {
- try conversation.refresh { (result) in
- switch result {
- case .success:
- if let members = conversation.members {
- print(members)
- }
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-Notes:
-
-You can only get member lists of **basic conversations**. Chat rooms and system conversations don't have member lists.
-
-## Querying Conversations with Custom Conditions
-
-There are more ways to get a `Conversation` besides listening to incoming events. You might want your users to search chat rooms by the names or locations of them, or to look for conversations that have certain members in them. All these requirements can be satisfied with the help of queries.
-
-### Queries on ID
-
-Here ID refers to the `objectId` in the `_Conversation` table. Since IDs are indexed, querying by ID is the easiest and most efficient way to look for a conversation:
-
-
-
-```cs
-var query = tom.GetQuery();
-var conversation = await query.Get("551260efe4b01608686c3e0f");
-```
-
-```java
-LCIMConversationsQuery query = tom.getConversationsQuery();
-query.whereEqualTo("objectId","551260efe4b01608686c3e0f");
-query.findInBackground(new LCIMConversationQueryCallback(){
- @Override
- public void done(List convs,LCIMException e){
- if(e==null){
- if(convs!=null && !convs.isEmpty()){
- // convs.get(0) is the conversation being found
- }
- }
- }
-});
-```
-
-```objc
-LCIMConversationQuery *query = [tom conversationQuery];
-[query getConversationById:@"551260efe4b01608686c3e0f" callback:^(LCIMConversation *conversation, NSError *error) {
- if (succeeded) {
- NSLog(@"Query completed!");
- }
-}];
-```
-
-```js
-tom
- .getConversation("551260efe4b01608686c3e0f")
- .then(function (conversation) {
- console.log(conversation.id);
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- let conversationQuery = tom.conversationQuery
- try conversationQuery.getConversation(by: "551260efe4b01608686c3e0f") { (result) in
- switch result {
- case .success(value: let conversation):
- print(conversation)
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-// We suggest you to get a conversation from the device's memory first to avoid making unnecessary network requests
-
-String convID = '551260efe4b01608686c3e0f';
-Conversation conversation = tom.conversationMap[convID];
-if (conversation == null) {
- try {
- ConversationQuery query = tom.conversationQuery();
- query.whereEqualTo('objectId', convID);
- conversation = await query.find();
- } catch (e) {
- print(e);
- }
-}
-```
-
-
-
-### Querying by Conditions
-
-There are a variety of ways for you to look for conversations that satisfy certain conditions.
-
-Let's start with `equalTo` which is the simplest method for querying conversations. The code below looks for all the conversations that have `type` (a string field) to be `private`:
-
-
-
-```cs
-var query = tom.GetQuery()
- .WhereEqualTo("type", "private");
-await query.Find();
-```
-
-```java
-LCIMConversationsQuery query = tom.getConversationsQuery();
-query.whereEqualTo("attr.type","private");
-// Perform query
-query.findInBackground(new LCIMConversationQueryCallback(){
- @Override
- public void done(List convs,LCIMException e){
- if(e == null){
- // convs contains all the results
- }
- }
-});
-```
-
-```objc
-LCIMConversationQuery *query = [tom conversationQuery];
-[query whereKey:@"attr.type" equalTo:@"private"];
-// Perform query
-[query findConversationsWithCallback:^(NSArray *objects, NSError *error) {
- NSLog(@"找到 %ld 个对话!", [objects count]);
-}];
-```
-
-```js
-var query = client.getQuery();
-query.equalTo("attr.type", "private");
-query
- .find()
- .then(function (conversations) {
- // conversations contains all the results
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- let conversationQuery = tom.conversationQuery
- try conversationQuery.where("attr.type", .equalTo("private"))
- try conversationQuery.findConversations { (result) in
- switch result {
- case .success(value: let conversations):
- print(conversations)
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- ConversationQuery query = jerry.conversationQuery();
- query.whereEqualTo('attr.type', 'private');
-// conversations contains all the results
- List conversations = await query.find();
-} catch (e) {
- print(e);
-}
-```
-
-
-
-The interface for querying conversations is very similar to that for querying objects in Data Storage. If you're already familiar with Data Storage, it shouldn't be hard for you to learn how to query conversations:
-
-- You can get query results with `find`
-- You can get number of results with `count`
-- You can get the first conversation satisfying conditions with `first`
-- You can implement pagination with `skip` and `limit`
-
-You can also apply conditions like "greater than", "greater than or equal to", "less than", and "less than or equal to" to `Number` and `Date` fields:
-
-
-
-| Logic | `AVIMConversationQuery` Method |
-| ------------------------ | ------------------------------ |
-| Equal to | `WhereEqualTo` |
-| Not equal to | `WhereNotEqualsTo` |
-| Greater than | `WhereGreaterThan` |
-| Greater than or equal to | `WhereGreaterThanOrEqualsTo` |
-| Less than | `WhereLessThan` |
-| Less than or equal to | `WhereLessThanOrEqualsTo` |
-
-| Logic | `LCIMConversationsQuery` Method |
-| ------------------------ | ------------------------------- |
-| Equal to | `whereEqualTo` |
-| Not equal to | `whereNotEqualsTo` |
-| Greater than | `whereGreaterThan` |
-| Greater than or equal to | `whereGreaterThanOrEqualsTo` |
-| Less than | `whereLessThan` |
-| Less than or equal to | `whereLessThanOrEqualsTo` |
-
-| Logic | `LCIMConversationQuery` Method |
-| ------------------------ | ------------------------------ |
-| Equal to | `equalTo` |
-| Not equal to | `notEqualTo` |
-| Greater than | `greaterThan` |
-| Greater than or equal to | `greaterThanOrEqualTo` |
-| Less than | `lessThan` |
-| Less than or equal to | `lessThanOrEqualTo` |
-
-| Logic | `Constraint` of `IMConversationQuery` |
-| ------------------------ | ------------------------------------- |
-| Equal to | `equalTo` |
-| Not equal to | `notEqualTo` |
-| Greater than | `greaterThan` |
-| Greater than or equal to | `greaterThanOrEqualTo` |
-| Less than | `lessThan` |
-| Less than or equal to | `lessThanOrEqualTo` |
-
-| Logic | `ConversationQuery` Method |
-| ------------------------ | -------------------------- |
-| Equal to | `equalTo` |
-| Not equal to | `notEqualTo` |
-| Greater than | `greaterThan` |
-| Greater than or equal to | `greaterThanOrEqualTo` |
-| Less than | `lessThan` |
-| Less than or equal to | `lessThanOrEqualTo` |
-
-
-
-Notes on default query conditions:
-
-When querying conversations, if there isn't any `where` condition specified, `ConversationQuery` will look for conversations containing the current user by default. Such a condition will be dismissed if any `where` condition is applied to the query. If you want to look for conversations containing certain `clientId`, you can follow the way introduced in [Queries on Array Values](#queries-on-array-values) to perform queries on `m` with the value of `clientId`. This won't cause any conflict with the default condition.
-
-### Using Regular Expressions
-
-You can use regular expressions as conditions when querying with `ConversationsQuery`. For example, to look for all the conversations that have `language` to be a Chinese character:
-
-
-
-```cs
-query.WhereMatches("language", "[\\u4e00-\\u9fa5]"); // language is a Chinese character
-```
-
-```java
-query.whereMatches("language","[\\u4e00-\\u9fa5]"); // language is a Chinese character
-```
-
-```objc
-[query whereKey:@"language" matchesRegex:@"[\\u4e00-\\u9fa5]"]; // language is a Chinese character
-```
-
-```js
-query.matches("language", /[\\u4e00-\\u9fa5]/); // language is a Chinese character
-```
-
-```swift
-try conversationQuery.where("language", .matchedRegularExpression("[\\u4e00-\\u9fa5]", option: nil))
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-### Queries on String Values
-
-You can look for conversations with string values that **start with** a particular string, which is similar to `LIKE 'keyword%'` in SQL. For example, to look for all the conversations with names starting with `education`:
-
-
-
-```cs
-query.WhereStartsWith("name", "education");
-```
-
-```java
-query.whereStartsWith("name","education");
-```
-
-```objc
-[query whereKey:@"name" hasPrefix:@"education"];
-```
-
-```js
-query.startsWith("name", "education");
-```
-
-```swift
-try conversationQuery.where("name", .prefixedBy("education"))
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-You can also look for conversations with string values that **include** a particular string, which is similar to `LIKE '%keyword%'` in SQL. For example, to look for all the conversations with names including `education`:
-
-
-
-```cs
-query.WhereContains("name", "education");
-```
-
-```java
-query.whereContains("name","education");
-```
-
-```objc
-[query whereKey:@"name" containsString:@"education"];
-```
-
-```js
-query.contains("name", "education");
-```
-
-```swift
-try conversationQuery.where("name", .matchedSubstring("education"))
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-If you want to look for conversations with string values that **exclude** a particular string, you can use [regular expressions](#using-regular-expressions). For example, to look for all the conversations with names excluding `education`:
-
-
-
-```cs
-query.WhereMatches("name", "^((?!education).)* $ ");
-```
-
-```java
-query.whereMatches("name","^((?!education).)* $ ");
-```
-
-```objc
-[query whereKey:@"name" matchesRegex:@"^((?!education).)* $ "];
-```
-
-```js
-var regExp = new RegExp("^((?!education).)*$", "i");
-query.matches("name", regExp);
-```
-
-```swift
-try conversationQuery.where("name", .matchedRegularExpression("^((?!education).)* $ ", option: nil))
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-### Queries on Array Values
-
-You can use `containsAll`, `containedIn`, and `notContainedIn` to perform queries on array values. For example, to look for all the conversations containing `Tom`:
-
-
-
-```cs
-var members = new List { "Tom" };
-query.WhereContainedIn("m", members);
-```
-
-```java
-query.whereContainedIn("m", Arrays.asList("Tom"));
-```
-
-```objc
-[query whereKey:@"m" containedIn:@[@"Tom"]];
-```
-
-```js
-query.containedIn("m", ["Tom"]);
-```
-
-```swift
-try conversationQuery.where("m", .containedIn(["Tom"]))
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-### Queries on Existence
-
-You can look for conversations with or without certain fields to be empty. For example, to look for all the conversations with `lm` to be empty:
-
-
-
-```cs
-query.WhereDoesNotExist("lm");
-```
-
-```java
-query.whereDoesNotExist("lm");
-```
-
-```objc
-[query whereKeyDoesNotExist:@"lm"];
-```
-
-```js
-query.doesNotExist("lm");
-```
-
-```swift
-try conversationQuery.where("lm", .notExisted)
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-Or, to look for all the conversations with `lm` not to be empty:
-
-
-
-```cs
-query.WhereExists("lm");
-```
-
-```java
-query.whereExists("lm");
-```
-
-```objc
-[query whereKeyExists:@"lm"];
-```
-
-```js
-query.exists("lm");
-```
-
-```swift
-try conversationQuery.where("lm", .existed)
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-### Compound Queries
-
-To look for all the conversations with `age` to be less than `18` and `keywords` containing `education`:
-
-
-
-```cs
-query.WhereContains("keywords", "education")
- .WhereLessThan("age", 18);
-```
-
-```java
-query.whereContains("keywords", "education");
-query.whereLessThan("age", 18);
-```
-
-```objc
-[query whereKey:@"keywords" containsString:@"education"];
-[query whereKey:@"age" lessThan:@(18)];
-```
-
-```js
-query.contains("keywords", "education").lessThan("age", 18);
-```
-
-```swift
-try conversationQuery.where("keywords", .matchedSubstring("education"))
-try conversationQuery.where("age", .lessThan(18))
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-You can also combine two queries with `and` or `or` to form a new query.
-
-For example, to look for all the conversations that either have `age` to be less than `18` or have `keywords` containing `education`:
-
-
-
-```cs
-// Not supported yet
-```
-
-```java
-LCIMConversationsQuery ageQuery = tom.getConversationsQuery();
-ageQuery.whereLessThan('age', 18);
-
-LCIMConversationsQuery keywordsQuery = tom.getConversationsQuery();
-keywordsQuery.whereContains('keywords', 'education');
-
-LCIMConversationsQuery query = LCIMConversationsQuery.or(Arrays.asList(priorityQuery, statusQuery));
-```
-
-```objc
-LCIMConversationQuery *ageQuery = [tom conversationQuery];
-[ageQuery whereKey:@"age" greaterThan:@(18)];
-
-LCIMConversationQuery *keywordsQuery = [tom conversationQuery];
-[keywordsQuery whereKey:@"keywords" containsString:@"education"];
-
-LCIMConversationQuery *query = [LCIMConversationQuery orQueryWithSubqueries:[NSArray arrayWithObjects:ageQuery,keywordsQuery,nil]];
-```
-
-```js
-// Not supported yet
-```
-
-```swift
-do {
- let ageQuery = tom.conversationQuery
- try ageQuery.where("age", .greaterThan(18))
-
- let keywordsQuery = tom.conversationQuery
- try keywordsQuery.where("keywords", .matchedSubstring("education"))
-
- let conversationQuery = try ageQuery.or(keywordsQuery)
-} catch {
- print(error)
-}
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-### Sorting
-
-You can sort the results of a query by ascending or descending order on certain fields. For example:
-
-
-
-```cs
-query.OrderByDescending("createdAt");
-```
-
-```java
-query.orderByDescending("createdAt");
-```
-
-```objc
-[query orderByDescending:@"createdAt"];
-```
-
-```js
-// Ascend by name and descend by creation time
-query.addAscending("name").addDescending("createdAt");
-```
-
-```swift
-try conversationQuery.where("createdAt", .descending)
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-### Excluding Member Lists from Results
-
-When searching conversations, you can exclude the lists of members from query results if you don't need them. By doing so, their `members` fields will become empty arrays. This helps you improve the speed of your app and reduces the network traffic needed.
-
-
-
-```cs
-query.Compact = true;
-```
-
-```java
-query.setCompact(true);
-```
-
-```objc
-query.option = LCIMConversationQueryOptionCompact;
-```
-
-```js
-query.compact(true);
-```
-
-```swift
-conversationQuery.options = [.notContainMembers]
-```
-
-```dart
-query.excludeMembers = true;
-```
-
-
-
-### Including Latest Messages in Results
-
-Many chatting apps show the latest message of each conversation in the conversation list. If you want a similar function in your app, you can turn on the option when querying conversations:
-
-
-
-```cs
-query.WithLastMessageRefreshed = true;
-```
-
-```java
-query.setWithLastMessagesRefreshed(true);
-```
-
-```objc
-query.option = LCIMConversationQueryOptionWithMessage;
-```
-
-```js
-// enable withLastMessagesRefreshed to include the latest message of each conversation in the results
-query.withLastMessagesRefreshed(true);
-```
-
-```swift
-conversationQuery.options = [.containLastMessage]
-```
-
-```dart
-query.includeLastMessage = true;
-```
-
-
-
-Keep in mind that what this option really does is to refresh the latest messages of conversations. Due to the existence of the cache, it is still possible for you to retrieve the outdated "latest messages" even though you set the option to be `false`.
-
-### Caching Results
-
-
-<>
-
-Not supported yet.
-
->
-<>
-
-By caching query results locally, if the device is offline, or if the app is just opened and the request for synchronizing with the cloud is not completed yet, there could still be some data available. You can also reduce the data usage of the user by performing queries with the cloud only when the app is first opened and having subsequent queries completed with local cache first.
-
-Keep in mind that query results will be fed with local cache first and will be synchronized with the cloud right after that. The expiration time for cache is 1 hour. You can configure cache with the following method provided by `LCIMConversationsQuery`:
-
-```java
-// Set caching policy for LCIMConversationsQuery
-public void setQueryPolicy(LCQuery.CachePolicy policy);
-```
-
-If you want cache to be accessed only when there's an error querying with the cloud, you can do this:
-
-```java
-LCIMConversationsQuery query = client.getConversationsQuery();
-query.setQueryPolicy(LCQuery.CachePolicy.NETWORK_ELSE_CACHE);
-query.findInBackground(new LCIMConversationQueryCallback() {
- @Override
- public void done(List conversations, LCIMException e) {
-
- }
-});
-```
-
->
-<>
-
-By caching query results locally, if the device is offline, or if the app is just opened and the request for synchronizing with the cloud is not completed yet, there could still be some data available. You can also reduce the data usage of the user by performing queries with the cloud only when the app is first opened and having subsequent queries completed with local cache first.
-
-Keep in mind that query results will be fed with local cache first and will be synchronized with the cloud right after that. The expiration time for cache is 1 hour. You can configure cache with the following method provided by `LCIMConversationQuery`:
-
-```objc
-// Set caching policy; defaults to kLCCachePolicyCacheElseNetwork
-@property (nonatomic) LCCachePolicy cachePolicy;
-
-// Set expiration time; defaults to 1 hour (1 * 60 * 60)
-@property (nonatomic) NSTimeInterval cacheMaxAge;
-```
-
-If you want cache to be accessed only when there's an error querying with the cloud, you can do this:
-
-```objc
-LCIMConversationQuery *query = [client conversationQuery];
-query.cachePolicy = kLCCachePolicyNetworkElseCache;
-[query findConversationsWithCallback:^(NSArray *objects, NSError *error) {
-
-}];
-```
-
-See [Data Storage Guide](/sdk/storage/guide/dotnet/) to learn more about the difference between all the caching policies.
-
->
-<>
-
-Conversations will be cached in memory using dictionaries according to their IDs. Such cache will not be persisted.
-
->
-<>
-
-The Swift SDK allows you to cache conversation to either memory or local storage.
-
-The code below caches conversations to memory:
-
-```swift
-client.getCachedConversation(ID: "CONVERSATION_ID") { (result) in
- switch result {
- case .success(value: let conversation):
- print(conversation)
- case .failure(error: let error):
- print(error)
- }
-}
-
-client.removeCachedConversation(IDs: ["CONVERSATION_ID"]) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-The code below caches conversations to local storage. **Note that when querying or deleting conversations stored in local storage, you need to call `prepareLocalStorage` and make sure the result is success; `prepareLocalStorage` only needs to be called once (for a result with success) and is often called between `IMClient.init()` and `IMClient.open()`**:
-
-```swift
-// Switch for Local Storage of IM Client
-do {
- // Client init with Local Storage feature
- let clientWithLocalStorage = try IMClient(ID: "CLIENT_ID")
-
- // Client init without Local Storage feature
- var options = IMClient.Options.default
- options.remove(.usingLocalStorage)
- let clientWithoutLocalStorage = try IMClient(ID: "CLIENT_ID", options: options)
-} catch {
- print(error)
-}
-
-// Preparation for Local Storage of IM Client
-do {
- try client.prepareLocalStorage { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-
-// Get and Load Stored Conversations to Memory
-do {
- try client.getAndLoadStoredConversations(completion: { (result) in
- switch result {
- case .success(value: let conversations):
- print(conversations)
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-
-// Delete Stored Conversations and Messages belong to them
-do {
- try client.deleteStoredConversationAndMessages(IDs: ["CONVERSATION_ID"], completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-Be aware that:
-
-- Chat rooms and temporary conversations are not cached.
-- Conversations have both in-memory cache and persistent (disk) cache. Messages only have in-memory cache, and only message query results are cached (if a message query has less than 3 results, it will not be cached).
-
->
-<>
-
-Not supported yet.
-
->
-
-
-
-### Optimizing Performance
-
-Since `Conversation` objects are stored on Data Storage, you can make use of indexes to improve the efficiency of querying, just like how you would do to other classes. Here are some suggestions for optimizing performance:
-
-- By default, indexes are created for `objectId`, `updatedAt`, and `createdAt` of `Conversation`, so querying by these fields would be naturally fast.
-- Although it's possible to implement pagination with `skip` and `limit`, the speed would slow down when the dataset grows larger. It would be more efficient to make use of `updatedAt` or `lastMessageAt` instead.
-- When searching for conversations containing a certain user by using `contains` on `m`, it's recommended that you stick to the default `limit` (which is 10) and make use of `updatedAt` or `lastMessageAt` for pagination.
-- If your app has too many conversations, consider creating a cloud function that periodically cleans up inactive conversations.
-
-## Retrieving Messages
-
-By default, message histories are stored on the cloud for **180** days. You may either pay to extend the period (contact us by submitting a ticket) or synchronize them to your own server with REST API.
-
-Our SDKs offer various ways for you to retrieve message histories. iOS and Android SDKs also provide a caching mechanism to help you reduce the number of queries you have to perform and display message histories to users even when their devices are offline.
-
-### Retrieving Messages Chronologically (New to Old)
-
-The most common way to retrieve messages is to fetch them from new to old with the help of pagination:
-
-
-
-```cs
-// limit could be any number from 1 to 100 (defaults to 20)
-var messages = await conversation.QueryMessages(limit: 10);
-foreach (var message in messages) {
- if (message is LCIMTextMessage textMessage) {
-
- }
-}
-```
-
-```java
-// limit could be any number from 1 to 100; invoking queryMessages without the limit parameter will retrieve 20 messages
-int limit = 10;
-conv.queryMessages(limit, new LCIMMessagesQueryCallback() {
- @Override
- public void done(List messages, LCIMException e) {
- if (e == null) {
- // The last 10 messages retrieved
- }
- }
-});
-```
-
-```objc
-// Retrieve the last 10 messages; limit could be any number from 1 to 100; use 0 for the default value (20)
-[conversation queryMessagesWithLimit:10 callback:^(NSArray *objects, NSError *error) {
- NSLog(@"Messages retrieved!");
-}];
-```
-
-```js
-conversation
- .queryMessages({
- limit: 10, // limit could be any number from 1 to 100 (defaults to 20)
- })
- .then(function (messages) {
- // The last 10 messages ordered from old to new
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- try conversation.queryMessage(limit: 10) { (result) in
- switch result {
- case .success(value: let messages):
- print(messages)
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-// limit could be any number from 1 to 100 (defaults to 20)
-try {
- List messages = await conversation.queryMessage(
- limit: 10,
- );
-} catch (e) {
- print(e);
-}
-```
-
-
-
-Here `queryMessage` supports pagination. Given the fact that you can locate a single message with its `messageId` and timestamp, this means that you can retrieve the next few messages after a given message by providing the `messageId` and timestamp of that message:
-
-
-
-```cs
-// limit could be any number from 1 to 1000 (defaults to 100)
-var messages = await conversation.QueryMessages(limit: 10);
-var oldestMessage = messages[0];
-var start = new LCIMMessageQueryEndpoint {
- MessageId = oldestMessage.Id,
- SentTimestamp = oldestMessage.SentTimestamp
-};
-var messagesInPage = await conversation.QueryMessages(start: start);
-```
-
-```java
-// limit could be any number from 1 to 1000 (defaults to 100)
-conv.queryMessages(10, new LCIMMessagesQueryCallback() {
- @Override
- public void done(List messages, LCIMException e) {
- if (e == null) {
- // The last 10 messages retrieved
- // The earliest message will be the first one
- LCIMMessage oldestMessage = messages.get(0);
-
- conv.queryMessages(oldestMessage.getMessageId(), oldestMessage.getTimestamp(),20,
- new LCIMMessagesQueryCallback(){
- @Override
- public void done(List messagesInPage,LCIMException e){
- if(e== null){
- // Query completed
- Log.d("Tom & Jerry", "got " + messagesInPage.size()+" messages");
- }
- }
- });
- }
- }
-});
-```
-
-```objc
-// Retrieve the last 10 messages
-[conversation queryMessagesWithLimit:10 callback:^(NSArray *messages, NSError *error) {
- NSLog(@"First retrieval completed!");
- // Get the messages right before the first message in the first page
- LCIMMessage *oldestMessage = [messages firstObject];
- [conversation queryMessagesBeforeId:oldestMessage.messageId timestamp:oldestMessage.sendTimestamp limit:10 callback:^(NSArray *messagesInPage, NSError *error) {
- NSLog(@"Second retrieval completed!");
- }];
-}];
-```
-
-```js
-// JS SDK encloses the feature into an iterator so you can keep retrieving new data by calling next
-// Create an iterator and retrieve 10 messages each time
-var messageIterator = conversation.createMessagesIterator({ limit: 10 });
-// Call next for the first time and get the first 10 messages; done equals to false means that there are more messages
-messageIterator
- .next()
- .then(function (result) {
- // result: {
- // value: [message1, ..., message10],
- // done: false,
- // }
- })
- .catch(console.error.bind(console));
-// Call next for the second time and get the 11th to 20th messages; done equals to false means that there are more messages
-// The iterator will keep track of the breaking point so you don't have to specify it
-messageIterator
- .next()
- .then(function (result) {
- // result: {
- // value: [message11, ..., message20],
- // done: false,
- // }
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- let start = IMConversation.MessageQueryEndpoint(
- messageID: "MESSAGE_ID",
- sentTimestamp: 31415926,
- isClosed: false
- )
- try conversation.queryMessage(start: start, limit: 10, completion: { (result) in
- switch result {
- case .success(value: let messages):
- print(messages)
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-List messages;
-try {
-// First query completed
- messages = await conversation.queryMessage(
- limit: 10,
- );
-} catch (e) {
- print(e);
-}
-
-try {
- // The earliest message will be the first one
- Message oldMessage = messages.first;
- // Get the messages right before the first message in the first page
- List messages2 = await conversation.queryMessage(
- startTimestamp: oldMessage.sentTimestamp,
- startMessageID: oldMessage.id,
- startClosed: true,
- limit: 10,
- );
-} catch (e) {
- print(e);
-}
-```
-
-
-
-### Retrieving Messages by Types
-
-Besides retrieving messages in time orders, you can also do that based on the types of messages. This could be helpful in scenarios like displaying all the images in a conversation.
-
-`queryMessage` can take in the type of messages:
-
-
-
-```cs
-// Pass in a generic type parameter and the SDK will automatically read the type and send it to the server for searching messages
-var imageMessages = await conversation.QueryMessages(messageType: -2);
-```
-
-```java
-int msgType = LCIMMessageType.IMAGE_MESSAGE_TYPE;
-conversation.queryMessagesByType(msgType, limit, new LCIMMessagesQueryCallback() {
- @Override
- public void done(List messages, LCIMException e){
- }
-});
-```
-
-```objc
-[conversation queryMediaMessagesFromServerWithType:LCIMMessageMediaTypeImage limit:10 fromMessageId:nil fromTimestamp:0 callback:^(NSArray *messages, NSError *error) {
- if (!error) {
- NSLog(@"Query completed!");
- }
-}];
-```
-
-```js
-conversation
- .queryMessages({ type: ImageMessage.TYPE })
- .then((messages) => {
- console.log(messages);
- })
- .catch(console.error);
-```
-
-```swift
-do {
- try conversation.queryMessage(limit: 10, type: IMTextMessage.messageType, completion: { (result) in
- switch result {
- case .success(value: let messages):
- print(messages)
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- List messages = await conversation.queryMessage(type: -2);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-To retrieve more images, follow the way introduced in the previous section to go through different pages.
-
-### Retrieving Messages Chronologically (Old to New)
-
-Besides the two ways mentioned above, you can also retrieve messages from old to new. The code below shows how you can retrieve messages starting from the time the conversation is created:
-
-
-
-```cs
-var earliestMessages = await conversation.QueryMessages(direction: LCIMMessageQueryDirection.OldToNew);
-```
-
-```java
-LCIMMessageInterval interval = new LCIMMessageInterval(null, null);
-conversation.queryMessages(interval, DirectionFromOldToNew, limit,
- new LCIMMessagesQueryCallback(){
- public void done(List messages, LCIMException exception) {
- // Handle result
- }
-});
-```
-
-```objc
-[conversation queryMessagesInInterval:nil direction:LCIMMessageQueryDirectionFromOldToNew limit:20 callback:^(NSArray * _Nullable messages, NSError * _Nullable error) {
- if (messages.count) {
- // Handle result
- }
-}];
-```
-
-```js
-var { MessageQueryDirection } = require('leancloud-realtime');
-conversation.queryMessages({
- direction: MessageQueryDirection.OLD_TO_NEW,
-}).then(function(messages) {
- // Handle result
-}.catch(function(error) {
- // Handle error
-});
-```
-
-```swift
-do {
- try conversation.queryMessage(direction: .oldToNew, limit: 10, completion: { (result) in
- switch result {
- case .success(value: let messages):
- print(messages)
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- List messages = await conversation.queryMessage(
- direction: MessageQueryDirection.oldToNew,
- );
-} catch (e) {
- print(e);
-}
-```
-
-
-
-It is a bit more complicated to implement pagination with this method. See the next section for details.
-
-### Retrieving Messages Chronologically (From a Timestamp to a Direction)
-
-You can retrieve messages starting from a given message (determined by ID and timestamp) toward a certain direction:
-
-- New to old: Retrieve messages sent **before** a given message
-- Old to new: Retrieve messages sent **after** a given message
-
-Now we can implement pagination in different directions.
-
-
-
-```cs
-var earliestMessages = await conversation.QueryMessages(direction: LCIMMessageQueryDirection.OldToNew, limit: 1);
-// Get messages sent after earliestMessages.Last()
-var start = new LCIMMessageQueryEndpoint {
- MessageId = earliestMessages.Last().Id
-};
-var nextPageMessages = await conversation.QueryMessages(start: start);
-```
-
-```java
-LCIMMessageIntervalBound start = LCIMMessageInterval.createBound(messageId, timestamp, false);
-LCIMMessageInterval interval = new LCIMMessageInterval(start, null);
-LCIMMessageQueryDirection direction;
-conversation.queryMessages(interval, direction, limit,
- new LCIMMessagesQueryCallback(){
- public void done(List messages, LCIMException exception) {
- // Handle result
- }
-});
-```
-
-```objc
-LCIMMessageIntervalBound *start = [[LCIMMessageIntervalBound alloc] initWithMessageId:nil timestamp:timestamp closed:false];
-LCIMMessageInterval *interval = [[LCIMMessageInterval alloc] initWithStartIntervalBound:start endIntervalBound:nil];
-[conversation queryMessagesInInterval:interval direction:direction limit:20 callback:^(NSArray * _Nullable messages, NSError * _Nullable error) {
- if (messages.count) {
- // Handle result
- }
-}];
-```
-
-```js
-var { MessageQueryDirection } = require('leancloud-realtime');
-conversation.queryMessages({
- startTime: timestamp,
- startMessageId: messageId,
-startClosed: false,
- direction: MessageQueryDirection.OLD_TO_NEW,
-}).then(function(messages) {
- // Handle result
-}.catch(function(error) {
- // Handle error
-});
-```
-
-```swift
-do {
- let start = IMConversation.MessageQueryEndpoint(
- messageID: "MESSAGE_ID",
- sentTimestamp: 31415926,
- isClosed: true
- )
- try conversation.queryMessage(start: start, direction: .oldToNew, limit: 10, completion: { (result) in
- switch result {
- case .success(value: let messages):
- print(messages)
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- List messages = await conversation.queryMessage(
- startTimestamp: textMessage.sentTimestamp,
- startMessageID: textMessage.id,
- startClosed: true,
- direction: MessageQueryDirection.oldToNew,
- limit: 10,
- );
-} catch (e) {
- print(e);
-}
-```
-
-
-
-### Retrieving Messages Within a Period of Time
-
-Besides retrieving messages chronologically, you can also retrieve messages within a period of time. For example, if you already have two messages, you can have one of them to be the starting point and another one to be the ending point to retrieve all the messages between them:
-
-Note: **The limit of 100 messages per query still applies here. To fetch more messages, keep changing the starting point or the ending point until all the messages are retrieved.**
-
-
-
-```cs
-var earliestMessage = await conversation.QueryMessages(direction: LCIMMessageQueryDirection.OldToNew, limit: 1);
-var latestMessage = await conversation.QueryMessages(limit: 1);
-var start = new LCIMMessageQueryEndpoint {
- MessageId = earliestMessage[0].Id
-};
-var end = new LCIMMessageQueryEndpoint {
- MessageId = latestMessage[0].Id
-};
-// messagesInInterval contains at most 100 messages
-var messagesInInterval = await conversation.QueryMessages(start: start, end: end);
-```
-
-```java
-LCIMMessageIntervalBound start = LCIMMessageInterval.createBound(messageId, timestamp, false);
-LCIMMessageIntervalBound end = LCIMMessageInterval.createBound(endMessageId, endTimestamp, false);
-LCIMMessageInterval interval = new LCIMMessageInterval(start, end);
-LCIMMessageQueryDirection direction;
-conversation.queryMessages(interval, direction, limit,
- new LCIMMessagesQueryCallback(){
- public void done(List messages, LCIMException exception) {
- // Handle result
- }
-});
-```
-
-```objc
-LCIMMessageIntervalBound *start = [[LCIMMessageIntervalBound alloc] initWithMessageId:nil timestamp:startTimestamp closed:false];
-LCIMMessageIntervalBound *end = [[LCIMMessageIntervalBound alloc] initWithMessageId:nil timestamp:endTimestamp closed:false];
-LCIMMessageInterval *interval = [[LCIMMessageInterval alloc] initWithStartIntervalBound:start endIntervalBound:end];
-[conversation queryMessagesInInterval:interval direction:direction limit:100 callback:^(NSArray * _Nullable messages, NSError * _Nullable error) {
- if (messages.count) {
- // Handle result
- }
-}];
-```
-
-```js
-conversation.queryMessages({
- startTime: timestamp,
- startMessageId: messageId,
- endTime: endTimestamp,
- endMessageId: endMessageId,
-}).then(function(messages) {
- // Handle result
-}.catch(function(error) {
- // Handle error
-});
-```
-
-```swift
-do {
- let start = IMConversation.MessageQueryEndpoint(
- messageID: "MESSAGE_ID_1",
- sentTimestamp: 31415926,
- isClosed: true
- )
- let end = IMConversation.MessageQueryEndpoint(
- messageID: "MESSAGE_ID_2",
- sentTimestamp: 31415900,
- isClosed: true
- )
- try conversation.queryMessage(start: start, end: end, completion: { (result) in
- switch result {
- case .success(value: let messages):
- print(messages)
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- List messages = await conversation.queryMessage(
- startTimestamp: textMessage.sentTimestamp,
- startMessageID: textMessage.id,
- startClosed: true,
- endTimestamp: fileMessage.sentTimestamp,
- endMessageID: fileMessage.id,
- endClosed: true,
- );
-} catch (e) {
- print(e);
-}
-```
-
-
-
-### Caching Messages
-
-iOS and Android SDKs come with a mechanism that automatically caches all the messages received and retrieved on the local device. It provides the following benefits:
-
-1. Message histories can be viewed even devices are offline
-2. The frequency of querying and the consumption of data can be minimized
-3. The speed for viewing messages can be increased
-
-Caching is enabled by default. You can turn it off with the following interface:
-
-
-
-```cs
-// Not supported yet
-```
-
-```java
-// Need to be set before calling LCIMClient.open(callback)
-LCIMOptions.getGlobalOptions().setMessageQueryCacheEnabled(false);
-```
-
-```objc
-// Need to be set before calling [avimClient openWithCallback:callback]
-avimClient.messageQueryCacheEnabled = false;
-```
-
-```js
-// Not supported yet
-```
-
-```swift
-// Switch for Local Storage of IM Client
-do {
- // Client init with Local Storage feature
- let clientWithLocalStorage = try IMClient(ID: "CLIENT_ID")
-
- // Client init without Local Storage feature
- var options = IMClient.Options.default
- options.remove(.usingLocalStorage)
- let clientWithoutLocalStorage = try IMClient(ID: "CLIENT_ID", options: options)
-} catch {
- print(error)
-}
-
-// Message Query Policy
-enum MessageQueryPolicy {
- case `default`
- case onlyNetwork
- case onlyCache
- case cacheThenNetwork
-}
-
-do {
- try conversation.queryMessage(policy: .default, completion: { (result) in
- switch result {
- case .success(value: let messages):
- print(messages)
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-## Logging out and Network Changes
-
-### Logging out
-
-If your app allows users to log out, you can use the `close` method provided by `LCIMClient` to properly close the connection to the cloud:
-
-
-
-```cs
-await tom.Close();
-```
-
-```java
-tom.close(new LCIMClientCallback(){
- @Override
- public void done(LCIMClient client,LCIMException e){
- if(e==null){
- // Logged out
- }
- }
-});
-```
-
-```objc
-[tom closeWithCallback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- NSLog(@"Logged out.");
- }
-}];
-```
-
-```js
-tom
- .close()
- .then(function () {
- console.log("Tom logged out.");
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-tom.close { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-```dart
-await tom.close();
-```
-
-
-
-After the function is called, the connection between the client and the server will be terminated. If you check the status of the corresponding `clientId` on the cloud, it would show as "offline".
-
-### Network Changes
-
-The availability of the messaging service is highly dependent on the Internet connection. If the connection is lost, all the operations regarding messages and conversations will fail. At this time, there need to be some indicators on the UI to tell the user about the network status.
-
-Our SDKs maintain a heartbeat mechanism with the cloud which detects the change of network status and have your app notified if certain events occur. To be specific, if the connection status changes (becomes lost or recovered), the following events will be populated:
-
-
-<>
-
-The following events will be populated on `LCIMClient`:
-
-- `OnPaused` occurs when the connection is lost. The messaging service is unavailable at this time.
-- `OnResume` occurs when the connection is recovered. The messaging service is available at this time.
-- `OnClose` occurs when the connection is closed and there will be no auto reconnection.
-
->
-<>
-
-The following events will be populated on `LCIMClientEventHandler`:
-
-- `onConnectionPaused()` occurs when the connection is lost. The messaging service is unavailable at this time.
-- `onConnectionResume()` occurs when the connection is recovered. The messaging service is available at this time.
-- `onClientOffline()` occurs when single-device sign-on is enabled and the current device is forced to go offline.
-
->
-<>
-
-The following events will be populated on `LCIMClientDelegate`:
-
-- `imClientResumed` occurs when the connection is recovered.
-- `imClientPaused` occurs when the connection is lost. Possible causes include a network problem occurred and the application goes into the background.
-- `imClientResuming` occurs when trying to reconnect.
-- `imClientClosed` occurs when the connection is closed and there will be no auto reconnection. Possible causes include there is a single-device login conflict or the client has been kicked off by the server.
-
-```objc
-- (void)imClientResumed:(LCIMClient *)imClient
-{
-
-}
-
-- (void)imClientResuming:(LCIMClient *)imClient
-{
-
-}
-
-- (void)imClientPaused:(LCIMClient *)imClient error:(NSError * _Nullable)error
-{
-
-}
-
-- (void)imClientClosed:(LCIMClient *)imClient error:(NSError * _Nullable)error
-{
-
-}
-```
-
->
-<>
-
-- `DISCONNECT`: Connection to the server is lost. The messaging service is unavailable at this time.
-- `OFFLINE`: Network is unavailable.
-- `ONLINE`: Network is recovered.
-- `SCHEDULE`: Scheduled to reconnect after a period of time. The messaging service is still unavailable at this time.
-- `RETRY`: Reconnecting.
-- `RECONNECT`: Connection to the server is recovered. The messaging service is available at this time.
-
-```js
-var { Event } = require("leancloud-realtime");
-
-realtime.on(Event.DISCONNECT, function () {
- console.log("Connection to the server is lost.");
-});
-realtime.on(Event.OFFLINE, function () {
- console.log("Network is unavailable.");
-});
-realtime.on(Event.ONLINE, function () {
- console.log("Network is recovered.");
-});
-realtime.on(Event.SCHEDULE, function (attempt, delay) {
- console.log(
- "Reconnecting in " + delay + " ms as attempt " + (attempt + 1) + "."
- );
-});
-realtime.on(Event.RETRY, function (attempt) {
- console.log("Reconnecting as attempt " + (attempt + 1) + ".");
-});
-realtime.on(Event.RECONNECT, function () {
- console.log("Connection to the server is recovered.");
-});
-```
-
->
-<>
-
-The following events will be populated on the `IMClientDelegate.client(_:event:)` method of `IMClientDelegate`:
-
-- `sessionDidOpen` occurs when the connection is recovered.
-- `sessionDidPause` occurs when the connection is lost. Possible causes include a network problem occurred and the application goes into the background.
-- `sessionDidResume` occurs when trying to reconnect.
-- `sessionDidClose` occurs when the connection is closed and there will be no auto reconnection. Possible causes include there is a single-device login conflict or the client has been kicked off by the server.
-
-```swift
-func client(_ client: IMClient, event: IMClientEvent) {
- switch event {
- case .sessionDidOpen:
- break
- case .sessionDidPause(error: let error):
- print(error)
- case .sessionDidResume:
- break
- case .sessionDidClose(error: let error):
- print(error)
- }
-}
-```
-
->
-<>
-
-The following events will be populated on `Client`:
-
-- `onOpened` occurs when the connection is established.
-- `onClosed` occurs when the connection is closed.
-- `onResuming` occurs when trying to reconnect. The messaging service is still unavailable at this time.
-- `onDisconnected` occurs when the connection is lost.
-
->
-
-
-
-## More Suggestions
-
-### Sorting Conversations by Last Activities
-
-In many scenarios you may need to sort conversations based on the time the last message in each of them is sent.
-
-There is a `lastMessageAt` property for each `LCIMConversation` (`lm` in the `_Conversation` table) which dynamically changes to reflect the time of the last message. The time is server-based (accurate to a second) so you don't have to worry about the time on the clients. `LCIMConversation` also offers a method for you to retrieve the last message of each conversation, which gives you more flexibility to design the UI of your app.
-
-### Auto Reconnecting
-
-If the connection between a client and the cloud is not properly closed, our iOS and Android SDKs will automatically reconnect when the network is recovered. You can listen to `IMClient` to get updated about the network status.
-
-### More Conversation Types
-
-Besides the [one-on-one chats](#one-on-one-chats) and [group chats](#group-chats) mentioned earlier, the following types of conversations are also supported:
-
-- Chat room: This can be used to build conversations that serve scenarios like live streaming. It's different from a basic group chat in the number of members supported and the deliverability promised. See [Chapter 3](/sdk/im/guide/senior/) for more details.
-
-- Temporary conversation: This can be used to build conversations between users and customer service representatives. It's different from a basic one-on-one chat in the fact that it has a shorter TTL which brings higher flexibility and lower cost (on data storage). See [Chapter 3](/sdk/im/guide/senior/) for more details.
-
-- System conversation: This can be used to build accounts that could broadcast messages to all their subscribers. It's different from a basic group chat in the fact that users can subscribe to it and there isn't a number limit of members. Subscribers can also send one-on-one messages to these accounts and these messages won't be seen by other users. See [Chapter 4](/sdk/im/guide/systemconv/) for more details.
-
-## Continue Reading
-
-- [2. Advanced Messaging Features, Push Notifications, Synchronization, and Multi-Device Sign-on](/sdk/im/guide/intermediate/)
-- [3. Security, Chat Rooms, and Temporary Conversations](/sdk/im/guide/senior/)
-- [4. Hooks and System Conversations](/sdk/im/guide/systemconv/)
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/intermediate.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/intermediate.mdx
deleted file mode 100644
index 7a33bdf19..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/intermediate.mdx
+++ /dev/null
@@ -1,2265 +0,0 @@
----
-title: 2. Advanced Messaging Features, Push Notifications, Synchronization, and Multi-Device Sign-on
-sidebar_label: Advanced Features
-sidebar_position: 2
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import { Conditional } from "/src/docComponents/conditional";
-
-## Introduction
-
-In the previous chapter, [Basic Conversations and Messages](/sdk/im/guide/beginner/), we introduced how you can create components in your app that support one-on-one chats and group chats, as well as how you can handle events triggered by the cloud. In this chapter, we will show you how to implement advanced features like:
-
-- Getting receipts when messages are delivered and read
-- Mentioning people with "@"
-- Recalling and editing messages
-- Push notifications and message synchronization
-- Single-device or multi-device sign-on
-- Sending messages of your custom types
-
-## Advanced Messaging Features
-
-If you are building an app for team collaboration or social networking, you may want more features to be included besides basic messaging. For example:
-
-- To mention someone with "@" so that they can easily find out what messages are important to them.
-- To edit or recall a message that has been sent out.
-- To send status messages like "Someone is typing".
-- To allow the sender of a message to know if it's delivered or read.
-- To synchronize messages sent to a receiver that has been offline.
-
-With Instant Messaging, you can easily implement the functions mentioned above.
-
-### Mentioning People
-
-Some group chats have a lot of messages going on and people may easily overlook the information that's important to them. That's why we need a way for senders to get people's attention.
-
-The most commonly used way to mention someone is to type "@ + name" when composing a message. But if we break it down, we'll notice that the "name" here is something determined by the app (it could be the real name or the nickname of the user) and could be totally different from the `clientId` identifying the user (since one is for people to see and one is for computers to read). A problem could be caused if someone changes their name at the moment another user sends a message with the old name mentioned. Besides this, we also need to consider the way to mention all the members in a conversation. Is it "@all"? "@group"? Or "@everyone"? Maybe all of them will be used, which totally depends on how the UI of the app is designed.
-
-So we cannot mention people by simply adding "@ + name" into a message. To walk through that, two additional properties are given to each message (`LCIMMessage`):
-
-- `mentionList`, an array of strings containing the list of `clientId`s being mentioned;
-- `mentionAll`, a `Bool` indicating whether all the members are mentioned.
-
-Depending on the logic of your app, it's possible for you to have both `mentionAll` to be set and `mentionList` to contain a list of members. Your app shall provide the UI that allows users to type in and select the members they want to mention. The only thing you need to do with the SDK is to call the setters of `mentionList` and `mentionAll` to set the members being mentioned. Here is a code example:
-
-
-
-```cs
-LCIMTextMessage textMessage = new LCIMTextMessage("@Tom Come back early.") {
- MentionIdList = new string[] { "Tom" }
-};
-await conversation.Send(textMessage);
-```
-
-```java
-String content = "@Tom Come back early.";
-LCIMTextMessage message = new LCIMTextMessage();
-message.setText(content);
-List list = new ArrayList<>(); // A list for holding the members being mentioned; you can add members into the list with the code below
-list.add("Tom");
-message.setMentionList(list);
-imConversation.sendMessage(message, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- }
-});
-```
-
-```objc
-LCIMMessage *message = [LCIMTextMessage messageWithText:@"@Tom Come back early." attributes:nil];
-message.mentionList = @[@"Tom"];
-[conversation sendMessage:message callback:^(BOOL succeeded, NSError * _Nullable error) {
- /* A message mentioning Tom has been sent */
-}];
-```
-
-```js
-const message = new TextMessage(`@Tom Come back early.`).setMentionList([
- "Tom",
-]);
-conversation
- .send(message)
- .then(function (message) {
- console.log("Sent!");
- })
- .catch(console.error);
-```
-
-```swift
-do {
- let message = IMTextMessage(text: "@Tom Come back early.")
- message.mentionedMembers = ["Tom"]
- try conversation.send(message: message, completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- TextMessage message = TextMessage();
- message.text = '@Tom Come back early.';
- message.mentionMembers = ['Tom'];
- await conversation.send(message: message);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-You can also mention everyone by setting `mentionAll`:
-
-
-
-```cs
-LCIMTextMessage textMessage = new LCIMTextMessage("@all") {
- MentionAll = true
-};
-await conv.Send(textMessage);
-```
-
-```java
-String content = "@all";
-LCIMTextMessage message = new LCIMTextMessage();
-message.setText(content);
-
-boolean mentionAll = true; // Indicates if everyone is mentioned
-message.mentionAll(mentionAll);
-
-imConversation.sendMessage(message, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- }
-});
-```
-
-```objc
-LCIMMessage *message = [LCIMTextMessage messageWithText:@"@all" attributes:nil];
-message.mentionAll = YES;
-[conversation sendMessage:message callback:^(BOOL succeeded, NSError * _Nullable error) {
- /* A message mentioning everyone has been sent */
-}];
-```
-
-```js
-const message = new TextMessage(`@all`).mentionAll();
-conversation
- .send(message)
- .then(function (message) {
- console.log("Sent!");
- })
- .catch(console.error);
-```
-
-```swift
-do {
- let message = IMTextMessage(text: "@all")
- message.isAllMembersMentioned = true
- try conversation.send(message: message, completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- TextMessage message = TextMessage();
- message.text = 'content';
- message.mentionAll = true;
- await conversation.send(message: message);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-The receiver of the message can call the getters of `mentionList` and `mentionAll` to see the members being mentioned:
-
-
-
-```cs
-jerry.onMessage = (conv, msg) => {
- List mentionIds = msg.MentionIdList;
-};
-```
-
-```java
-@Override
-public void onMessage(LCIMAudioMessage msg, LCIMConversation conv, LCIMClient client) {
- // Get a list of clientIds being mentioned
- List currentMsgMentionUserList = message.getMentionList();
-}
-```
-
-```objc
-// The code below shows how you can get a list of clientIds being mentioned in an LCIMTypedMessage; the code can be modified to serve other types inherited from LCIMMessage
-- (void)conversation:(LCIMConversation *)conversation didReceiveTypedMessage:(LCIMTypedMessage *)message {
- // Get a list of clientIds being mentioned
- NSArray *mentionList = message.mentionList;
-}
-```
-
-```js
-client.on(Event.MESSAGE, function messageEventHandler(message, conversation) {
- var mentionList = receivedMessage.getMentionList();
-});
-```
-
-```swift
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case .message(event: let messageEvent):
- switch messageEvent {
- case .received(message: let message):
- if let mentionedMembers = message.mentionedMembers {
- print(mentionedMembers)
- }
- if let isAllMembersMentioned = message.isAllMembersMentioned {
- print(isAllMembersMentioned)
- }
- default:
- break
- }
- default:
- break
- }
-}
-```
-
-```dart
-jerry.onMessage = ({
- Client client,
- Conversation conversation,
- Message message,
-}) {
- List mentionList = message.mentionMembers;
-};
-```
-
-
-
-To make it easier to display things on the UI, the following two flags are offered by `LCIMMessage` to indicate the status of mentioning:
-
-- `mentionedAll`: Whether all the members in the conversation are mentioned. Becomes `true` only when `mentionAll` is true, otherwise it remains `false`.
-- `mentioned`: Whether the current user is mentioned. Becomes `true` when `mentionList` contains the `clientId` of the current user or when `mentionAll` is `true`, otherwise it remains `false`.
-
-Here is a code example:
-
-
-
-```cs
-client.OnMessage = (conv, msg) => {
- bool mentioned = msg.MentionAll || msg.MentionList.Contains("Tom");
-};
-```
-
-```java
-@Override
-public void onMessage(LCIMAudioMessage msg, LCIMConversation conv, LCIMClient client) {
- // Check if all the members in the conversation are mentioned
- boolean currentMsgMentionAllUsers = message.isMentionAll();
- // Check if the current user is mentioned
- boolean currentMsgMentionedMe = message.mentioned();
-}
-```
-
-```objc
-// The code below shows how you can check if all the members in the conversation or the current user is mentioned in an LCIMTypedMessage; the code can be modified to serve other types inherited from LCIMMessage
-- (void)conversation:(LCIMConversation *)conversation didReceiveTypedMessage:(LCIMTypedMessage *)message {
- // Check if all the members in the conversation are mentioned
- BOOL mentionAll = message.mentionAll;
- // Check if the current user is mentioned
- BOOL mentionedMe = message.mentioned;
-}
-```
-
-```js
-client.on(Event.MESSAGE, function messageEventHandler(message, conversation) {
- var mentionedAll = receivedMessage.mentionedAll;
- var mentionedMe = receivedMessage.mentioned;
-});
-```
-
-```swift
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case .message(event: let messageEvent):
- switch messageEvent {
- case .received(message: let message):
- print(message.isCurrentClientMentioned)
- default:
- break
- }
- default:
- break
- }
-}
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-### Modify a Message
-
-To allow users to edit the messages they sent, you need to enable **Allow editing messages with SDK** on **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Instant Messaging settings**. There are no limits on the time within which users can perform this operation. However, users are only allowed to edit the messages they sent, not the ones others sent.
-
-To modify a message, what you would do is not update the original message instance, but create a new one and call `Conversation#updateMessage(oldMessage, newMessage)` to submit the request to the cloud. Here is a code example:
-
-
-
-```cs
-LCIMTextMessage newMessage = new LCIMTextMessage("The new message.");
-await conversation.UpdateMessage(oldMessage, newMessage);
-```
-
-```java
-LCIMTextMessage textMessage = new LCIMTextMessage();
-textMessage.setContent("The new message.");
-imConversation.updateMessage(oldMessage, textMessage, new LCIMMessageUpdatedCallback() {
- @Override
- public void done(LCIMMessage avimMessage, LCException e) {
- if (null == e) {
- // The message is updated; avimMessage is the updated message
- }
- }
-});
-```
-
-```objc
-LCIMMessage *oldMessage = <#MessageYouWantToUpdate#>;
-LCIMMessage *newMessage = [LCIMTextMessage messageWithText:@"Just a new message" attributes:nil];
-
-[conversation updateMessage:oldMessage
- toNewMessage:newMessage
- callback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- NSLog(@"The message is updated.");
- }
-}];
-```
-
-```js
-var newMessage = new TextMessage("new message");
-conversation
- .update(oldMessage, newMessage)
- .then(function () {
- // The message is updated
- })
- .catch(function (error) {
- // Handle error
- });
-```
-
-```swift
-do {
- let newMessage = IMTextMessage(text: "Just a new message")
- try conversation.update(oldMessage: oldMessage, to: newMessage, completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- Message updatedMessage = await conversation.updateMessage(
- oldMessage: oldMessage,
- newMessage: newMessage,
- );
-} catch (e) {
- print(e);
-}
-```
-
-
-
-If the modification succeeded, other members in the conversation will receive a `MESSAGE_UPDATE` event:
-
-
-
-```cs
-tom.OnMessageUpdated = (conv, msg) => {
- if (msg is LCIMTextMessage textMessage) {
- WriteLine($"Content: {textMessage.Text}; Message ID: {textMessage.Id}");
- }
-};
-```
-
-```java
-void onMessageUpdated(LCIMClient client, LCIMConversation conversation, LCIMMessage message) {
- // message is the updated message
-}
-```
-
-```objc
-/* A delegate method for handling events of editing messages */
-- (void)conversation:(LCIMConversation *)conversation messageHasBeenUpdated:(LCIMMessage *)message reason:(LCIMMessagePatchedReason * _Nullable)reason {
- /* A message is updated */
-}
-```
-
-```js
-var { Event } = require("leancloud-realtime");
-conversation.on(Event.MESSAGE_UPDATE, function (newMessage, reason) {
- // newMessage is the updated message
- // Look for the original message with its ID and replace it with newMessage
- // reason (optional) is the reason the message is edited
- // If reason is not specified, it means the sender edited the message
- // If the code of reason is positive, it means a hook on Cloud Engine caused the edit
- // (the code value can be specified when defining hooks)
- // If the code of reason is negative, it means a built-in mechanism of the system caused the edit
- // For example, -4408 means the message is edited due to text moderation
- // The detail of reason is a string containing the explanation
-});
-```
-
-```swift
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case .message(event: let messageEvent):
- switch messageEvent {
- case let .updated(updatedMessage: updatedMessage, reason: _):
- print(updatedMessage)
- default:
- break
- }
- default:
- break
- }
-}
-```
-
-```dart
-tom.onMessageUpdated = ({
- Client client,
- Conversation conversation,
- Message updatedMessage,
- int patchCode,
- String patchReason,
-}) {
- // updatedMessage is the updated message
-};
-```
-
-
-
-For Android and iOS SDKs, if caching is enabled (it is enabled by default), the SDKs will first update the modified message in the cache and then trigger an event to the app. When you receive such an event, simply refresh the chatting page to reflect the latest collection of messages.
-
-If a message is modified by the system (for example, due to text moderation or by a hook on Cloud Engine), the sender will receive a `MESSAGE_UPDATE` event, and other members in the conversation will receive the modified message.
-
-### Recall a Message
-
-Besides modifying a sent message, a user can also recall a message they sent.
-Similarly, you need to enable **Allow recalling messages with SDK** on **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Instant Messaging settings**.
-Also, there are no limits on the time within which users can perform this operation, and users are only allowed to recall the messages they sent, not the ones others sent.
-
-To recall a message, invoke the `Conversation#recallMessage` method:
-
-
-
-```cs
-await conversation.RecallMessage(message);
-```
-
-```java
-conversation.recallMessage(message, new LCIMMessageRecalledCallback() {
- @Override
- public void done(LCIMRecalledMessage recalledMessage, LCException e) {
- if (null == e) {
- // The message is recalled; the UI may be updated now
- }
- }
-});
-```
-
-```objc
-LCIMMessage *oldMessage = <#MessageYouWantToRecall#>;
-
-[conversation recallMessage:oldMessage callback:^(BOOL succeeded, NSError * _Nullable error, LCIMRecalledMessage * _Nullable recalledMessage) {
- if (succeeded) {
- NSLog(@"The message is recalled.");
- }
-}];
-```
-
-```js
-conversation
- .recall(oldMessage)
- .then(function (recalledMessage) {
- // The message is recalled
- // recalledMessage is a RecalledMessage
- })
- .catch(function (error) {
- // Handle error
- });
-```
-
-```swift
-do {
- try conversation.recall(message: oldMessage, completion: { (result) in
- switch result {
- case .success(value: let recalledMessage):
- print(recalledMessage)
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- RecalledMessage recalledMessage = await conversation.recallMessage(
- message: oldMessage,
- );
-} catch (e) {
- print(e);
-}
-```
-
-
-
-Once a message is recalled, other members in the conversation will receive the `MESSAGE_RECALL` event:
-
-
-
-```cs
-tom.OnMessageRecalled = (conv, recalledMsg) => {
- // recalledMsg is the message being recalled
-};
-```
-
-```java
-void onMessageRecalled(LCIMClient client, LCIMConversation conversation, LCIMMessage message) {
- // message is the message being recalled
-}
-```
-
-```objc
-/* A delegate method for handling events of recalling messages */
-- (void)conversation:(LCIMConversation *)conversation messageHasBeenRecalled:(LCIMRecalledMessage *)message reason:(LCIMMessagePatchedReason * _Nullable)reason {
- /* A message is recalled */
-}
-```
-
-```js
-var { Event } = require("leancloud-realtime");
-conversation.on(Event.MESSAGE_RECALL, function (recalledMessage, reason) {
- // recalledMessage is the message being recalled
- // Look for the original message with its ID and replace it with recalledMessage
- // reason (optional) is the reason the message is recalled; see the part for editing messages for more details
-});
-```
-
-```swift
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case .message(event: let messageEvent):
- switch messageEvent {
- case let .updated(updatedMessage: updatedMessage, reason: _):
- if let recalledMessage = updatedMessage as? IMRecalledMessage {
- print(recalledMessage)
- }
- default:
- break
- }
- default:
- break
- }
-}
-```
-
-```dart
-tom.onMessageRecalled = ({
- Client client,
- Conversation conversation,
- RecalledMessage recalledMessage,
-}) {
- // recalledMessage is the message being recalled
-};
-```
-
-
-
-For Android and iOS SDKs, if caching is enabled (it is enabled by default), the SDKs will first delete the recalled message from the cache and then trigger an event to the app. This ensures the consistency of data internally. When you receive such an event, simply refresh the chatting page to reflect the latest collection of messages. Depending on your implementation, either the recalled message will simply disappear, or an indicator saying the message has been recalled will take the original message's place.
-
-### Transient Messages
-
-Sometimes we need to send status updates like "Someone is typing…" or "Someone changed the group name to XX". Different from messages sent by users, these messages don't need to be stored in the history, nor do they need to be guaranteed to be delivered (if members are offline or there is a network error, it would be okay if these messages are not delivered). Such messages are best sent as transient messages.
-
-Transient message is a special type of message. It has the following differences compared to a basic message:
-
-- It won't be stored in the cloud so it couldn't be retrieved from history messages.
-- It's only delivered to those who are online. Offline members cannot receive it later or get push notifications about it.
-- It's not guaranteed to be delivered. If there's a network error preventing the message from being delivered, the server won't make a second attempt.
-
-Therefore, transient messages are best for communicating real-time updates of statuses that are changing frequently or implementing simple control protocols.
-
-The way to construct a transient message is the same as that for a basic message. The only difference is the way it is being sent. So far we have shown the following way of sending messages with `LCIMConversation`:
-
-
-
-```cs
-public async Task Send(LCIMMessage message, LCIMMessageSendOptions options = null);
-```
-
-```java
-/**
- * Send a message
- */
-public void sendMessage(LCIMMessage message, final LCIMConversationCallback callback)
-```
-
-```objc
-/*!
- Send a message
- */
-- (void)sendMessage:(LCIMMessage *)message
- callback:(void (^)(BOOL succeeded, NSError * _Nullable error))callback;
-```
-
-```js
-/**
- * Send a message
- * @param {Message} message The message itself; an instance of Message or its subtype
- * @return {Promise.} The message being sent
- */
-async send(message)
-```
-
-```swift
-/// Send Message.
-///
-/// - Parameters:
-/// - message: The message to be sent.
-/// - options: @see `MessageSendOptions`.
-/// - priority: @see `IMChatRoom.MessagePriority`.
-/// - pushData: The push data of APNs.
-/// - progress: The file uploading progress.
-/// - completion: callback.
-public func send(message: IMMessage, options: MessageSendOptions = .default, priority: IMChatRoom.MessagePriority? = nil, pushData: [String : Any]? = nil, progress: ((Double) -> Void)? = nil, completion: @escaping (LCBooleanResult) -> Void) throws
-```
-
-```dart
-Future send({
- @required Message message,
-}) async {}
-```
-
-
-
-In fact, an additional parameter `LCIMMessageOption` can be provided when sending a message. Here is a complete list of interfaces offered by `LCIMConversation`:
-
-
-
-```cs
-///
-/// Sends a message in this conversation.
-///
-/// The message to send.
-///
-public async Task Send(LCIMMessage message, LCIMMessageSendOptions options = null);
-```
-
-```java
-/**
- * Send a message
- * @param message
- * @param messageOption
- * @param callback
- */
-public void sendMessage(final LCIMMessage message, final LCIMMessageOption messageOption, final LCIMConversationCallback callback);
-```
-
-```objc
-/*!
- Send a message
- @param message - The message itself
- @param option - Options
- @param callback - Callback
- */
-- (void)sendMessage:(LCIMMessage *)message
- option:(nullable LCIMMessageOption *)option
- callback:(void (^)(BOOL succeeded, NSError * _Nullable error))callback;
-```
-
-```js
-/**
- * Send a message
- * @param {Message} message The message itself; an instance of Message or its subtype
- * @param {Object} [options] since v3.3.0; Options
- * @param {Boolean} [options.transient] since v3.3.1; Whether it is a transient message
- * @param {Boolean} [options.receipt] Whether receipts are needed
- * @param {Boolean} [options.will] since v3.4.0; Whether it is a will message
- * A will message will be sent when the user loses connection
- * @param {MessagePriority} [options.priority] The priority of the message; for chat rooms only
- * see: {@link module:leancloud-realtime.MessagePriority MessagePriority}
- * @param {Object} [options.pushData] The content for push notification; if the receiver is offline, a push notification with this content will be triggered
- * @return {Promise.} The message being sent
- */
-async send(message, options)
-```
-
-```swift
-/// Message Sending Option
-public struct MessageSendOptions: OptionSet {
- /// Get Receipt when other client received message or read message.
- public static let needReceipt = MessageSendOptions(rawValue: 1 << 0)
-
- /// Indicates whether this message is transient.
- public static let isTransient = MessageSendOptions(rawValue: 1 << 1)
-
- /// Indicates whether this message will be auto delivering to other client when this client disconnected.
- public static let isAutoDeliveringWhenOffline = MessageSendOptions(rawValue: 1 << 2)
-}
-
-/// Send Message.
-///
-/// - Parameters:
-/// - message: The message to be sent.
-/// - options: @see `MessageSendOptions`.
-/// - priority: @see `IMChatRoom.MessagePriority`.
-/// - pushData: The push data of APNs.
-/// - progress: The file uploading progress.
-/// - completion: callback.
-public func send(message: IMMessage, options: MessageSendOptions = .default, priority: IMChatRoom.MessagePriority? = nil, pushData: [String : Any]? = nil, progress: ((Double) -> Void)? = nil, completion: @escaping (LCBooleanResult) -> Void) throws
-```
-
-```dart
-Future send({
- @required Message message,
- bool transient,
- bool receipt,
- bool will,
- MessagePriority priority,
- Map pushData,
-}) async {}
-```
-
-
-
-With `LCIMMessageOption`, we can specify:
-
-- Whether it is a transient message (field `transient`).
-- Whether receipts are needed (field `receipt`; more details will be covered later).
-- The priority of the message (field `priority`; more details will be covered later).
-- Whether it is a will message (field `will`; more details will be covered later).
-- The content for push notification (field `pushData`; more details will be covered later); if the receiver is offline, a push notification with this content will be triggered.
-
-The code below sends a transient message saying "Tom is typing…" to the conversation when Tom's input box gets focused:
-
-
-
-```cs
-LCIMTextMessage textMessage = new LCIMTextMessage("Tom is typing…");
-LCIMMessageSendOptions option = new LCIMMessageSendOptions() {
- Transient = true
-};
-await conversation.Send(textMessage, option);
-```
-
-```java
-String content = "Tom is typing…";
-LCIMTextMessage message = new LCIMTextMessage();
-message.setText(content);
-
-LCIMMessageOption option = new LCIMMessageOption();
-option.setTransient(true);
-
-imConversation.sendMessage(message, option, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- }
-});
-```
-
-```objc
-LCIMMessage *message = [LCIMTextMessage messageWithText:@"Tom is typing…" attributes:nil];
-LCIMMessageOption *option = [[LCIMMessageOption alloc] init];
-option.transient = true;
-[conversation sendMessage:message option:option callback:^(BOOL succeeded, NSError * _Nullable error) {
- /* A transient message is sent */
-}];
-```
-
-```js
-const message = new TextMessage("Tom is typing…");
-conversation.send(message, { transient: true });
-```
-
-```swift
-do {
- let message = IMTextMessage(text: "Tom is typing…")
- try conversation.send(message: message, options: [.isTransient], completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- TextMessage message = TextMessage();
- message.text = 'Tom is typing…';
-// Send a transient message
- await conversation.send(message: message, transient: true);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-The procedure for receiving transient messages is the same as that for basic messages. You can run different logic based on the types of messages. The example above sets the type of the message to be text message, but it would be better if you assign a distinct type to it. Our SDK doesn't offer a type for transient messages, so you may build your own depending on what you need. See [Custom Message Types](#custom-message-types) for more details.
-
-### Receipts
-
-When the cloud is delivering messages, it follows the sequence the messages are pushed to the cloud and delivers the former messages before the latter ones (FIFO). Our internal protocol also requires that the SDK sends an acknowledgment (ack) back to the cloud for every single message received by it. If a message is received by the SDK but the ack is not received by the cloud due to a packet loss, the cloud would assume that the message is not successfully delivered and will keep redelivering it until an ack is received. Correspondingly, the SDK also does its work to make duplicate messages insensible by the app. The entire mechanism ensures that no messages will be lost in the entire delivery process.
-
-However, in certain scenarios, functionality beyond the one mentioned above is demanded. For example, a sender may want to know when the receiver got the message and when they opened the message. In a product for team collaboration or private communication, a sender may even want to monitor the real-time status of every message sent out by them. Such requirements can be satisfied with the help of receipts.
-
-Similar to the way of sending transient messages, if you want receipts to be given back, you need to specify an option in `LCIMMessageOption`:
-
-
-
-```cs
-LCIMTextMessage textMessage = new LCIMTextMessage("A very important message.");
-LCIMMessageSendOptions option = new LCIMMessageSendOptions {
- Receipt = true
-};
-await conversation.Send(textMessage, option);
-```
-
-```java
-LCIMMessageOption messageOption = new LCIMMessageOption();
-messageOption.setReceipt(true);
-imConversation.sendMessage(message, messageOption, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- }
-});
-```
-
-```objc
-LCIMMessageOption *option = [[LCIMMessageOption alloc] init];
-option.receipt = true;
-[conversation sendMessage:message option:option callback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"Message sent with receipts requested.");
- }
-}];
-```
-
-```js
-var message = new TextMessage("A very important message.");
-conversation.send(message, {
- receipt: true,
-});
-```
-
-```swift
-do {
- let message = IMTextMessage(text: "A very important message.")
- try conversation.send(message: message, options: [.needReceipt], completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- TextMessage message = TextMessage();
- message.text = 'A very important message.';
- await conversation.send(message: message, receipt: true);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-> Note:
->
-> Receipts are not enabled by default. You need to manually turn that on when sending each message. Receipts are only available for conversations with no more than 2 members.
-
-So how do senders handle the receipts they get?
-
-#### Delivery Receipts
-
-When a message is delivered to the receiver, the cloud will give a delivery receipt to the sender. **Keep in mind that this is not the same as a read receipt.**
-
-
-
-```cs
-// Tom creates an LCIMClient with his name as clientId
-LCIMClient client = new LCIMClient("Tom");
-// Tom logs in
-await client.Open();
-
-// Enable delivery receipt
-client.OnMessageDelivered = (conv, msgId) => {
- // Things to do after messages are delivered
-};
-// Send message
-LCIMTextMessage textMessage = new LCIMTextMessage("Wanna go to the bakery tonight?");
-await conversation.Send(textMessage);
-```
-
-```java
-public class CustomConversationEventHandler extends LCIMConversationEventHandler {
- /**
- * Handle notifications for messages being delivered
- */
- public void onLastDeliveredAtUpdated(LCIMClient client, LCIMConversation conversation) {
- ;
- }
-}
-
-// Set up global event handler
-LCIMMessageManager.setConversationEventHandler(new CustomConversationEventHandler());
-```
-
-```objc
-// Implement `conversation:messageDelivered` to listen if there are messages delivered
-- (void)conversation:(LCIMConversation *)conversation messageDelivered:(LCIMMessage *)message {
- NSLog(@"%@", @"Message delivered."); // Print log
-}
-```
-
-```js
-var { Event } = require("leancloud-realtime");
-conversation.on(Event.LAST_DELIVERED_AT_UPDATE, function () {
- console.log(conversation.lastDeliveredAt);
- // Update the UI to mark all the messages before lastDeliveredAt to be "delivered"
-});
-```
-
-```swift
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case .message(event: let messageEvent):
- switch messageEvent {
- case let .delivered(toClientID: toClientID, messageID: messageID, deliveredTimestamp: deliveredTimestamp):
- if messageID == message.ID {
- message.deliveredTimestamp = deliveredTimestamp
- }
- default:
- break
- }
- default:
- break
- }
-}
-```
-
-```dart
-tom.onMessageDelivered = ({
- Client client,
- Conversation conversation,
- String messageID,
- String toClientID,
- DateTime atDate,
-}) {
- // Things to do after the message is delivered
-};
-```
-
-
-
-The content included in the receipt will not be a specific message. Instead, it will be the time the messages in the current conversation are last delivered (`lastDeliveredAt`). We have mentioned earlier that messages are delivered according to the sequence they are pushed to the cloud. Therefore, given the time of the last delivery, we can infer that all the messages sent before it are delivered. On the UI of the app, you can mark all the messages sent before `lastDeliveredAt` to be "delivered".
-
-#### Read Receipts
-
-When we say a message is delivered, what we mean is that the message is received by the client from the cloud. At this time, the actual user might not have the conversation page or even the app open (Android apps can receive messages in the background). So we cannot assume that a message is read just because it is delivered.
-
-Therefore, we offer another kind of receipt showing if a receiver has actually seen a message.
-
-Again, since messages are delivered in time order, we don't have to check if every single message is being read. Think about a scenario like this:
-
-![A pop-up with the title "Welcome Back" says "You have 5002 unread messages. Would you like to skip them all? (Select 'Yes' to mark them as read)". There are two buttons on the bottom: "Yes" and "No".](/img/io/realtime_read_confirm.png)
-
-When a user opens a conversation, we can say that the user has read all the messages in it. You can use the following interface of `Conversation` to mark all the messages in it as read:
-
-
-
-```cs
-///
-/// Mark the last message of this conversation as read.
-///
-///
-public Task Read();
-```
-
-```java
-/**
- * Mark as read
- */
-public void read();
-```
-
-```objc
-/*!
- Mark as read
- This method marks the latest message sent into a conversation by the other member as read. The sender of the message will get a read receipt.
- */
-- (void)readInBackground;
-```
-
-```js
-/**
- * Mark as read
- * @return {Promise.} self
- */
-async read();
-```
-
-```swift
-/// Clear unread messages that its sent timestamp less than the sent timestamp of the parameter message.
-///
-/// - Parameter message: The default is the last message.
-public func read(message: IMMessage? = nil)
-```
-
-```dart
-await conversation.read();
-```
-
-
-
-After the receiver has read the latest messages, the sender will get a receipt indicating that the messages they have sent out are read.
-
-So if Tom is chatting with Jerry and wants to know if Jerry has read the messages, the following procedure would apply:
-
-1. Tom sends a message to Jerry and requests receipts on it:
-
-
-
- ```cs
- LCIMTextMessage textMessage = new LCIMTextMessage("A very important message.");
- LCIMMessageSendOptions options = new LCIMMessageSendOptions {
- Receipt = true
- };
- await conversation.Send(textMessage);
- ```
-
- ```java
- LCIMClient tom = LCIMClient.getInstance("Tom");
- LCIMConversation conv = client.getConversation("551260efe4b01608686c3e0f");
-
- LCIMTextMessage textMessage = new LCIMTextMessage();
- textMessage.setText("Hello, Jerry!");
-
- LCIMMessageOption option = new LCIMMessageOption();
- option.setReceipt(true); /* Request receipts */
-
- conv.sendMessage(textMessage, option, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- if (e == null) {
- /* Sent */
- }
- }
- });
- ```
-
- ```objc
- LCIMMessageOption *option = [[LCIMMessageOption alloc] init];
- option.receipt = YES; /* Request receipts */
-
- LCIMTextMessage *message = [LCIMTextMessage messageWithText:@"Hello, Jerry!" attributes:nil];
-
- [conversation sendMessage:message option:option callback:^(BOOL succeeded, NSError * _Nullable error) {
- if (!error) {
- /* Sent */
- }
- }];
- ```
-
- ```js
- var message = new TextMessage("A very important message.");
- conversation.send(message, {
- receipt: true,
- });
- ```
-
- ```swift
- do {
- let message = IMTextMessage(text: "Hello, Jerry!")
- try conversation.send(message: message, options: [.needReceipt], completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
- } catch {
- print(error)
- }
- ```
-
- ```dart
- try {
- TextMessage message = TextMessage();
- message.text = 'A very important message.';
- await conversation.send(message: message, receipt: true);
- } catch (e) {
- print(e);
- }
- ```
-
-
-
-2. Jerry reads Tom's message and calls `read` on the conversation to mark the latest messages as read:
-
-
-
- ```cs
- await conversation.Read();
- ```
-
- ```java
- conversation.read();
- ```
-
- ```objc
- [conversation readInBackground];
- ```
-
- ```js
- conversation
- .read()
- .then(function (conversation) {})
- .catch(console.error.bind(console));
- ```
-
- ```swift
- conversation.read()
- ```
-
- ```dart
- await conversation.read();
- ```
-
-
-
-3. Tom gets a read receipt with the conversation's `lastReadAt` updated. The UI can be updated to mark all messages sent before `lastReadAt` to be read:
-
-
-
- ```cs
- tom.OnLastReadAtUpdated = (conv) => {
- // The message is read by Jerry; the time Jerry read messages for the last time can be retrieved by calling conversation.LastReadAt
- };
- ```
-
- ```java
- public class CustomConversationEventHandler extends LCIMConversationEventHandler {
- /**
- * Handle notifications for messages being read
- */
- public void onLastReadAtUpdated(LCIMClient client, LCIMConversation conversation) {
- /* The message is read by Jerry; the time Jerry read messages for the last time can be retrieved by calling conversation.getLastReadAt() */
- }
- }
-
- // Set up the global event handler
- LCIMMessageManager.setConversationEventHandler(new CustomConversationEventHandler());
- ```
-
- ```objc
- // Tom can get the update of lastReadAt within the delegate method of client
- - (void)conversation:(LCIMConversation *)conversation didUpdateForKey:(LCIMConversationUpdatedKey)key {
- if ([key isEqualToString:LCIMConversationUpdatedKeyLastReadAt]) {
- NSDate *lastReadAt = conversation.lastReadAt;
- /* The message is read by Jerry; lastReadAt can be used to update UI; for example, to mark all the messages before lastReadAt to be "read" */
- }
- }
- ```
-
- ```js
- var { Event } = require("leancloud-realtime");
- conversation.on(Event.LAST_READ_AT_UPDATE, function () {
- console.log(conversation.lastReadAt);
- // Update the UI to mark all the messages before lastReadAt to be "read"
- });
- ```
-
- ```swift
- func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case .message(event: let messageEvent):
- switch messageEvent {
- case let .read(byClientID: byClientID, messageID: messageID, readTimestamp: readTimestamp):
- if messageID == message.ID {
- message.readTimestamp = readTimestamp
- }
- default:
- break
- }
- default:
- break
- }
- }
- ```
-
- ```dart
- jerry.onLastReadAtUpdated = ({
- Client client,
- Conversation conversation,
- }) {
- // Update the UI to mark all the messages before lastReadAt to be "read"
- };
- ```
-
-
-
-Note:
-
-To use read receipts, turn on [notifications on updates of unread message count](#notifications-on-updates-of-unread-message-count) when initializing your app.
-
-### Muting Conversations
-
-If a user doesn't want to receive notifications from a conversation but still wants to stay in it, they can mute the conversation. See [Muting Conversations](/sdk/im/guide/senior/) in the next chapter for more details.
-
-### Will Messages
-
-Will message can be used to automatically notify other members in a conversation when a user goes offline unexpectedly. It gets its name from the wills filed by testators, giving people a feeling that the last messages of a person should always be heard. It looks like the message saying "Tom is offline and cannot receive messages" in this image:
-
-![In a conversation named "Tom & Jerry", Jerry receives a will message saying "Tom is offline and cannot receive messages". The will message looks like a system notification and shares a different style with other messages.](/img/io/lastwill-message.png)
-
-A will message needs to be composed ahead of time and cached on the cloud. The cloud doesn't send it out immediately after receiving it. Instead, it waits until the sender of it goes offline unexpectedly. You can implement your own logic to handle such an event.
-
-
-
-```cs
-LCIMTextMessage message = new LCIMTextMessage("I am a will message. I will be sent out to other members in the conversation when the sender goes offline unexpectedly.");
-LCIMMessageSendOptions options = new LCIMMessageSendOptions {
- Will = true
-};
-await conversation.Send(message, options);
-```
-
-```java
-LCIMTextMessage message = new LCIMTextMessage();
-message.setText("I am a will message. I will be sent out to other members in the conversation when the sender goes offline unexpectedly.");
-
-LCIMMessageOption option = new LCIMMessageOption();
-option.setWill(true);
-
-conversation.sendMessage(message, option, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- if (e == null) {
- // Sent
- }
- }
-});
-```
-
-```objc
-LCIMMessageOption *option = [[LCIMMessageOption alloc] init];
-option.will = YES;
-
-LCIMMessage *willMessage = [LCIMTextMessage messageWithText:@"I am a will message. I will be sent out to other members in the conversation when the sender goes offline unexpectedly." attributes:nil];
-
-[conversation sendMessage:willMessage option:option callback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- NSLog(@"Sent!");
- }
-}];
-```
-
-```js
-var message = new TextMessage(
- "I am a will message. I will be sent out to other members in the conversation when the sender goes offline unexpectedly."
-);
-conversation
- .send(message, { will: true })
- .then(function () {
- // Sent; other members will see the message once the current client goes offline unexpectedly
- })
- .catch(function (error) {
- // Handle error
- });
-```
-
-```swift
-do {
- let message = IMTextMessage(text: "I am a will message. I will be sent out to other members in the conversation when the sender goes offline unexpectedly.")
- try conversation.send(message: message, options: [.isAutoDeliveringWhenOffline], completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- TextMessage message = TextMessage();
- message.text = 'I am a will message. I will be sent out to other members in the conversation when the sender goes offline unexpectedly.';
- await conversation.send(message: message, will: true);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-Once the sender goes offline unexpectedly, other members will immediately receive the will message. You can design your own way to display it on the UI.
-
-Will message has the **following restrictions**:
-
-- Each user can only have one will message set up at a time. This means that if a user sets will messages for multiple conversations or multiple will messages for the same conversation, only the last one will take effect.
-- Will messages don't get stored in the history.
-- If a user logs out proactively, the will message set by this user will not be sent out (if there is one).
-
-### Text Moderation
-
-
-
-If your app allows users to create group chats, you might consider filtering cuss words from the messages sent by users. Instant Messaging offers a built-in component that helps you easily implement this function. See [Text Moderation](/sdk/im/guide/senior/) in the next chapter for more details.
-
-
-
-
-
-See [Text Moderation](/sdk/im/guide/senior/) in the next chapter for more details.
-
-
-
-### Handling Undelivered Messages
-
-Sometimes you may need to store the messages that are not successfully sent out into a local cache and handle them later. For example, if a client's connection to the server is lost and a message cannot be sent out due to this, you may still keep the message locally. Perhaps you can add an error icon and a button for retrying next to the message displayed on the UI. The user may tap on the button when the connection is recovered to make another attempt to send the message.
-
-By default, both Android and iOS SDKs enable a local cache for storing messages. The cache stores all the messages that are already sent to the cloud and keeps itself updated with the data in the cloud. To make things easier, undelivered messages can also be stored in the same cache.
-
-The code below adds a message to the cache:
-
-
-
-```cs
-// Not supported yet
-```
-
-```java
-conversation.addToLocalCache(message);
-```
-
-```objc
-[conversation addMessageToCache:message];
-```
-
-```js
-// Not supported yet
-```
-
-```swift
-do {
- try conversation.insertFailedMessageToCache(failedMessage) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-The code below removes a message from the cache:
-
-
-
-```cs
-// Not supported yet
-```
-
-```java
-conversation.removeFromLocalCache(message);
-```
-
-```objc
-[conversation removeMessageFromCache:message];
-```
-
-```js
-// Not supported yet
-```
-
-```swift
-do {
- try conversation.removeFailedMessageFromCache(failedMessage) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-When reading messages from the cache, you can make messages look different on the UI based on the property `message.status`. If the `status` of a message is `LCIMMessageStatusFailed`, it means the message cannot be sent out, so you can add a button for retrying on the UI. An additional benefit of using the local cache is that the SDK will make sure the same message only gets sent out once. This ensures that there won't be any duplicate messages on the cloud.
-
-## Push Notifications
-
-If your users are using your app on mobile devices, they might close the app at any time, which prevents you from delivering new messages to them in the ordinary way. At this time, using push notifications becomes a good alternative to get users notified when new messages are coming in.
-
-If you are building an iOS or Android app, you can utilize the built-in push notification services offered by these operating systems, as long as you have your certificates configured for iOS or have the function enabled for Android. Check the following pages for more details:
-
-1. [Instant Messaging Overview](/sdk/im/guide/overview/)
-2. [Android Push Notification Guide](/sdk/push/guide/android-mixpush/) / [iOS Push Notification Guide](/sdk/push/guide/ios/)
-
-The cloud will associate the `clientId`s of users with the data in the `_Installation` table that keeps track of the devices. When a user sends a message to a conversation, the cloud will automatically convert the message to a push notification and send it to those who are offline but are using iOS devices or using Android devices with push notification services enabled. We also allow you to connect third-party push notification services to your app.
-
-The highlight of this feature is that you can **customize the contents of push notifications**. You have the following three ways to specify the contents:
-
-1. Setting up a static message
-
- You can fill in a global static JSON string on **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Push notifications** for delivering push notifications with a static message. For example, if you put:
-
- ```json
- { "alert": "New message received", "badge": "Increment" }
- ```
-
- Then whenever there is a new message going to an offline user, the user will receive a push notification saying "New message received".
-
- Keep in mind that `badge` is for iOS devices only which means to increase the number displayed on the badge of the app. Its value, `Increment`, is case-sensitive.
- Typically, when an end user opens or closes the application, you need to set the value of the badge field of the `_Installation` class to zero, which clears the badge number.
-
- Besides, you can also customize the sounds of push notifications for iOS devices.
-
-2. Specifying contents when sending messages from a client
-
- When using the first way introduced above, the content included in each push notification is the same regardless of the message being sent out. Is it possible to dynamically generate these contents to make them relevant to the actual messages?
-
- Remember how we specified `LCIMMessageOption` when sending transient messages? The same parameter takes in a `pushData` property which allows you to specify the contents of push notifications. Here is a code example:
-
-
-
-```cs
-LCIMTextMessage message = new LCIMTextMessage("Hey Jerry, me and Kate are gonna watch a game at a bar tonight. Wanna come with us?");
-LCIMMessageSendOptions sendOptions = new LCIMMessageSendOptions {
- PushData = new Dictionary {
- { "alert", "New message received"},
- { "category", "Message"},
- { "badge", 1},
- { "sound", "message.mp3"}, // The name of the file for the sound; has to be present in the app
- { "custom-key", "This is a custom attribute with custom-key being the name of the key. You can use your own names for keys."}
- }
-};
-```
-
-```java
-LCIMTextMessage msg = new LCIMTextMessage();
-msg.setText("Hey Jerry, me and Kate are gonna watch a game at a bar tonight. Wanna come with us?");
-
-LCIMMessageOption messageOption = new LCIMMessageOption();
-String pushMessage = "{\"alert\":\"New message received\", \"category\":\"Message\","
- + "\"badge\":1,\"sound\":\"message.mp3\","
- + "\"custom-key\":\"This is a custom attribute with custom-key being the name of the key. You can use your own names for keys.\"}";
-messageOption.setPushData(pushMessage);
-conv.sendMessage(msg, messageOption, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- if (e == null) {
- // Sent
- }
- }
-});
-```
-
-```objc
-LCIMMessageOption *option = [[LCIMMessageOption alloc] init];
-option.pushData = @{@"alert" : @"New message received", @"sound" : @"message.mp3", @"badge" : @1, @"custom-key" : @"This is a custom attribute with custom-key being the name of the key. You can use your own names for keys."};
-[conversation sendMessage:[LCIMTextMessage messageWithText:@"Hey Jerry, me and Kate are gonna watch a game at a bar tonight. Wanna come with us?" attributes:nil] option:option callback:^(BOOL succeeded, NSError * _Nullable error) {
- // Handle result and error
-}];
-```
-
-```js
-const message = new TextMessage('Hey Jerry, me and Kate are gonna watch a game at a bar tonight. Wanna come with us?');
-conversation.send(message), {
- pushData: {
- "alert": "New message received",
- "category": "Message",
- "badge": 1,
- "sound": "message.mp3", // The name of the file for the sound; has to be present in the app
- "custom-key": "This is a custom attribute with custom-key being the name of the key. You can use your own names for keys."
- }
-});
-```
-
-```swift
-do {
- let message = IMTextMessage(text: "Hey Jerry, me and Kate are gonna watch a game at a bar tonight. Wanna come with us?")
- let pushData: [String: Any] = [
- "alert": "New message received",
- "category": "Message",
- "badge": 1,
- "sound": "message.mp3",
- "custom-key": "This is a custom attribute with custom-key being the name of the key. You can use your own names for keys."
- ]
- try conversation.send(message: message, pushData: pushData, completion: { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- TextMessage message = TextMessage();
- message.text = 'Hey Jerry, me and Kate are gonna watch a game at a bar tonight. Wanna come with us?';
- await conversation.send(message: message, pushData: {
- "alert": "New message received",
- "category": "Message",
- "badge": 1,
- "sound": "message.mp3", // The name of the file for the sound; has to be present in the app
- "custom-key": "This is a custom attribute with custom-key being the name of the key. You can use your own names for keys."
- });
-} catch (e) {
- print(e);
-}
-```
-
-
-
-3. Generating contents dynamically on the server side
-
- The second way introduced above allows you to compose the contents of push notifications based on the messages being sent, but the logic needs to be predefined on the client side, which makes things less flexible.
-
- So we offer a third way that allows you to define the logic of push notifications on the server side with the help of hooks. Check [this page](/sdk/im/guide/systemconv/) for more details.
-
-Here is a comparison of the priorities of the three methods mentioned above: **Generating contents dynamically on the server side > Specifying contents when sending messages from a client > Setting up a static message**.
-
-If more than one of these methods are implemented at the same time, the push notifications generated on the server side will always get the highest priority. Those sent from clients will get a lower priority, and the static message set up on the dashboard will get the lowest priority.
-
-### Implementations and Restrictions
-
-If your app is using push notification services together with Instant Messaging, whenever a client logs in, the SDK will automatically associate the `clientId` with the device information (stored in the `Installation` table) by having the device **subscribe** to the channel with `clientId` as its name. The association can be found in the `channels` field of the `_Installation` table. By doing so, when the cloud wants to send a push notification to a client, the client's device can be targeted by the `clientId` associated with it.
-
-Since Instant Messaging generates way more push notifications than other sources, the cloud will not keep any records of them, nor can you find them on **Developer Center** > **Your game** > **Game Services** > **Cloud Services** > **Push Notification** > **Push records**.
-
-Each push notification is only valid for 7 days. This means that if a device doesn't connect to the cloud for more than 7 days, it will not receive this push notification anymore.
-
-### Other Settings for Push Notifications
-
-By default, push notifications are sent to the production environment of APNs for iOS devices.
-Use `"_profile": "dev"` if you want to switch to the development environment of APNs (the development certificate will be used if certificate-based authentication is selected):
-
-```json
-{
- "alert": "New message received",
- "_profile": "dev"
-}
-```
-
-When push notifications are sent via Token Authentication, if your app has private keys for multiple Team IDs, please confirm the one that should be used for your target devices and fill it into the `_apns_team_id` parameter, since Apple doesn't allow a single request to include push notifications sent to devices belonging to different Team IDs.
-
-```json
-{
- "alert": "New message received",
- "_apns_team_id": "my_fancy_team_id"
-}
-```
-
-The `_profile` and `_apns_team_id` attributes are used internally by the push service and neither will actually be used.
-When specifying additional push messages, different push messages are supported for different kinds of devices (e.g. `ios`, `android`). Keep in mind that the internal attributes, `_profile` and `_apns_team_id`, should not be specified inside the `ios` object, otherwise they will not take effect.
-As an example, a push message like this will cause the message to be pushed to APNs’ production environment:
-
-```json
-{
- "ios": {
- "badge": "Increment",
- "category": "NEW_CHAT_MESSAGE",
- "sound": "default",
- "thread-id": "chat",
- "alert": {
- "title": "New message received",
- "body": "This message will still be pushed to the APNs production environment because of the incorrect location of the internal property _profile."
- },
- "_profile": "dev"
- },
- "android": {
- "title": "New message received",
- "alert": ""
- }
-}
-```
-
-To push to the development environment:
-
-```json
-{
- "_profile": "dev",
- "ios": {
- /* … */
- },
- "android": {
- /* … */
- }
-}
-```
-
-You can insert certain built-in variables into the content you enter on **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Push notifications**. By doing this, you can embed certain context into the content of push notifications:
-
-- `${convId}` The ID of the conversation
-- `${timestamp}` The Unix timestamp when the push notification is triggered
-- `${fromClientId}` The `clientId` of the sender
-
-## Message Synchronization
-
-Push notification seems to be a good way to remind users of new messages, but the actual messages won't get delivered until the user goes online. If a user hasn't been online for an extremely long time, there will be tons of messages piled up on the cloud. How can we make sure that all these messages will be properly delivered once the user goes online?
-
-Instant Messaging provides a way for clients to pull messages from the cloud. The cloud keeps track of the last message each user receives from each conversation. When a user goes online, the conversations containing new messages as well as the number of unread messages in each of them will be computed and the client will receive a notification indicating that there is an update on the total number of unread messages. The client can then proactively fetch these messages.
-
-### Notifications on Updates of Unread Message Count
-
-When the client goes online, the cloud will compute the numbers of unread messages of all the conversations the client belongs to.
-
-To receive such notifications, the client needs to indicate that it is using the method of pulling messages from the cloud. As mentioned earlier, the JavaScript, Android, and iOS SDKs use this method by default, so there isn't any configuration that needs to be done.
-
-The SDK will maintain an `unreadMessagesCount` field on each `IMConversation` to track the number of unread messages in the conversation.
-
-When the client goes online, the cloud will drop in a series of `` in the format of events indicating updates on the numbers of unread messages. Each of them matches a conversation containing new messages and serves as the initial value of a `` maintained on the client side. After this, whenever a message is received by the SDK, the corresponding `unreadMessageCount` will be automatically increased. When the number of unread messages of a conversation is cleared, both `` on the cloud and maintained by the SDK will be reset.
-
-> If the count of unread messages is enabled, it will keep increasing until you explicitly reset it.
-> It will not be reset automatically if the client goes offline again.
-> Even if a message is received when the client is online, the count will still increase.
-> Make sure to reset the count by marking conversations as read whenever needed.
-
-When the number of `` changes, the SDK will send an `UNREAD_MESSAGES_COUNT_UPDATE` event to the app through `IMClient`. You can listen to this event and make corresponding changes to the number of unread messages on the UI. We recommend you cache unread counts at the application level, and whenever there are two different counts available for the same conversation, replace the older data with the newer one.
-
-
-
-```cs
-tom.OnUnreadMessagesCountUpdated = (convs) => {
- foreach (LCIMConversation conv in convs) {
- // conv.Unread is the number of unread messages in conversation
- }
-};
-```
-
-```java
-// Implement the delegate method onUnreadMessagesCountUpdated of LCIMConversationEventHandler to receive notifications on updates of unread message count
-onUnreadMessagesCountUpdated(LCIMClient client, LCIMConversation conversation) {
- // conversation.getUnreadMessagesCount() is the number of unread messages in conversation
-}
-```
-
-```objc
-// Use delegate method conversation:didUpdateForKey: to observe the unreadMessagesCount property of the conversation
-- (void)conversation:(LCIMConversation *)conversation didUpdateForKey:(LCIMConversationUpdatedKey)key {
- if ([key isEqualToString:LCIMConversationUpdatedKeyUnreadMessagesCount]) {
- NSUInteger unreadMessagesCount = conversation.unreadMessagesCount;
- /* New messages exist; update UI or fetch messages */
- }
-}
-```
-
-```js
-var { Event } = require("leancloud-realtime");
-client.on(Event.UNREAD_MESSAGES_COUNT_UPDATE, function (conversations) {
- for (let conv of conversations) {
- console.log(conv.id, conv.name, conv.unreadMessagesCount);
- }
-});
-```
-
-```swift
-func client(_ client: IMClient, conversation: IMConversation, event: IMConversationEvent) {
- switch event {
- case .unreadMessageCountUpdated:
- print(conversation.unreadMessageCount)
- default:
- break
- }
-}
-```
-
-```dart
-tom.onUnreadMessageCountUpdated = ({
- Client client,
- Conversation conversation,
-}) {
- // conversation.unreadMessageCount is the number of unread messages in conversation
-};
-```
-
-
-
-When responding to an `UNREAD_MESSAGES_COUNT_UPDATE` event, you get a `Conversation` object containing the `lastMessage` property which is the last message received by the current user from the conversation. To display the actual unread messages, [fetch the messages](/sdk/im/guide/beginner/) that come after it.
-
-The only way to clear the number of unread messages is to mark the messages as read with `Conversation#read`. You may do so when:
-
-- The user opens a conversation
-- The user is already in a conversation and a new message comes in
-
-Implementation details on unread message counts for iOS and Android SDKs:
-
-iOS SDKs (Objective-C and Swift) will fetch all `UNREAD_MESSAGES_COUNT_UPDATE` events provided by the cloud on login, while Android SDK only fetches the latest events generated after the previous fetch (Android SDK remembers the timestamp of the last fetch).
-
-Therefore, Android developers need to cache the events for unread messages at the application level, because the events of some conversations have been fetched on previous logins, but not the current one. For iOS developers, they need to do the same thing because the cloud tracks at most 50 conversations containing unread messages, and the events for unread messages are only available for those conversations. If the events for unread messages are not cached, some conversations may have inaccurate counts of unread messages.
-
-## Multi-Device Sign-on and Single-Device Sign-on
-
-In some scenarios, a user can stay logged in on multiple devices at the same time. In other ones, a user can be logged in on only one device at a time. With Instant Messaging, you can easily implement both **multi-device sign-on** and **single-device sign-on** depending on your needs.
-
-When creating an `IMClient` instance, you can provide a `tag` parameter besides `clientId` to have the cloud check the uniqueness of `` when logging the user in. If the user is already logged in on another device with the same `tag`, the cloud will log the user out from that device, otherwise the user will stay logged in on all their devices. When a user is logged in on multiple devices, the messages coming to this user will be delivered to all these devices and the numbers of unread messages will be synchronized. If the user sends a message from one of these devices, the message will appear on other devices as well.
-
-Based on the above mechanism, a variety of requirements can be met with Instant Messaging:
-
-1. Multi-Device Sign-on: If `tag` is not specified when logging in, a user will be able to have any number of devices logged in at the same time.
-2. Single-Device Sign-on: If all the clients share the same `tag`, a user will be able to stay logged in on only one device at a time.
-3. Multi-Device Sign-on With Restrictions: You can assign a unique `tag` for each type of device. For example, if you have `Mobile` for phones, `Pad` for tablets, and `Web` for desktop computers, a user will be able to stay logged in on three devices with different types, but not two desktop computers.
-
-### Setting Tags
-
-The code below sets a `tag` called `Mobile` when creating `IMClient`, which can be used for the mobile client of your app:
-
-
-
-```cs
-LCIMClient client = new LCIMClient(clientId, "Mobile", "your-device-id");
-```
-
-```java
-// Provide tag as the second parameter
-LCIMClient currentClient = LCIMClient.getInstance(clientId, "Mobile");
-currentClient.open(new LCIMClientCallback() {
- @Override
- public void done(LCIMClient avimClient, LCIMException e) {
- if(e == null){
- // Successfully logged in
- }
- }
-});
-```
-
-```objc
-NSError *error;
-LCIMClient *currentClient = [[LCIMClient alloc] initWithClientId:@"Tom" tag:@"Mobile" error:&error];
-if (!error) {
- [currentClient openWithCallback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- // Successfully logged in
- }
- }];
-}
-```
-
-```js
-realtime.createIMClient("Tom", { tag: "Mobile" }).then(function (tom) {
- console.log("Tom logged in.");
-});
-```
-
-```swift
-do {
- let client = try IMClient(ID: "CLIENT_ID", tag: "Mobile")
- client.open { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- Client tom = Client(id: 'Tom', tag: 'Mobile');
- await tom.open();
-} catch (e) {
- print(e);
-}
-```
-
-
-
-With the code above, if a user logs in on one mobile device and then logs in on another one (with the same `tag`), the user will be logged out from the former one.
-
-### Handling Conflicts
-
-When the cloud encounters the same `` for a second time, the device used for the earlier one will be logged out and receive a `CONFLICT` event:
-
-
-
-```cs
-tom.OnClose = (code, detail) => {
-
-};
-```
-
-```java
-public class AVImClientManager extends LCIMClientEventHandler {
- /**
- * Implementing this method to handle the event of being logged out
- *
- *
- * @param client
- * @param code The status code indicating the reason of being logged out
- */
- @Override
- public void onClientOffline(LCIMClient avimClient, int i) {
- if(i == 4111){
- // Tell the user that the same clientId is logged in on another device
- }
- }
-}
-
-// Need to register the custom LCIMClientEventHandler to receive onClientOffline notifications
-LCIMClient.setClientEventHandler(new AVImClientManager());
-```
-
-```objc
-- (void)imClientClosed:(LCIMClient *)imClient error:(NSError * _Nullable)error
-{
- if ([error.domain isEqualToString:kLeanCloudErrorDomain] &&
- error.code == 4111) {
- // Tell the user that the same clientId is logged in on another device
- }
-}
-```
-
-```js
-var { Event } = require("leancloud-realtime");
-tom.on(Event.CONFLICT, function () {
- // Tell the user that the same clientId is logged in on another device
-});
-```
-
-```swift
-func client(_ client: IMClient, event: IMClientEvent) {
- switch event {
- case .sessionDidClose(error: let error):
- if error.code == 4111 {
- // Tell the user that the same clientId is logged in on another device
- }
- default:
- break
- }
-}
-```
-
-```dart
-tom.onClosed = ({
- Client client,
- RTMException exception,
-}) {
- if (exception.code == '4111') {
- // Tell the user that the same clientId is logged in on another device
- }
-};
-```
-
-
-
-The reason a device gets logged out will be included in the event so that you can display a message to the user with that.
-
-All the "logins" mentioned above refer to users' explicit logins. If the user has already logged in, when the application restarts or reconnects, the SDK will relogin automatically. Under these scenarios, if a login conflict is encountered, the cloud will not log out earlier devices. The device trying to relogin will receive an error instead.
-
-Similarly, if you want an explicit login to receive an error when encountering a conflict, you can pass a special parameter on login:
-
-
-
-```cs
-await tom.Open(false);
-```
-
-```java
-LCIMClientOpenOption openOption = new LCIMClientOpenOption();
-openOption.setReconnect(true);
-LCIMClient currentClient = LCIMClient.getInstance(clientId, "Mobile");
-currentClient.open(openOption, new LCIMClientCallback() {
- @Override
- public void done(LCIMClient avimClient, LCIMException e) {
- if(e == null){
- // Connected
- }
- }
-});
-```
-
-```objc
-NSError *err;
-LCIMClient *currentClient = [[LCIMClient alloc] initWithClientId:@"Tom" tag:@"Mobile" error:&err];
-if (err) {
- NSLog(@"init failed with error: %@", err);
-} else {
- [currentClient openWithOption:LCIMClientOpenOptionReopen callback:^(BOOL succeeded, NSError * _Nullable error) {
- if ([error.domain isEqualToString:kLeanCloudErrorDomain] &&
- error.code == 4111) {
- // Failed to log in; the previously logged in device will not be logged out
- }
- }];
-}
-```
-
-```js
-realtime
- .createIMClient("Tom", { tag: "Mobile", isReconnect: true })
- .then(function (tom) {
- console.log(
- "Failed to log in; the previously logged in device will not be logged out"
- );
- });
-```
-
-```swift
-do {
- let client = try IMClient(ID: "Tom", tag: "Mobile")
- client.open(options: [.reconnect]) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- if error.code == 4111 {
- // Failed to log in; the previously logged in device will not be logged out
- }
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- Client tom = Client(id: 'Tom', tag: 'Mobile');
- // Failed to log in; the previously logged in device will not be logged out
- await tom.open(reconnect: true);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-## Custom Message Types
-
-Although Instant Messaging already supports a number of common message types by default, you can still define your own types as you need. For example, if you want your users to send messages containing payments and contacts, you can implement that with custom message types.
-
-### Custom Message Attributes
-
-The following message types are offered by default:
-
-- `TextMessage` Text message
-- `ImageMessage` Image message
-- `AudioMessage` Audio message
-- `VideoMessage` Video message
-- `FileMessage` File message (.txt, .doc, .md, etc.)
-- `LocationMessage` Location message
-
-When composing messages with these types, you can include additional information by attaching custom attributes in the format of key-value pairs. For example, if you are sending a message and need to include city information, you can put it into `attributes` of the message rather than create your own message type.
-
-
-
-```cs
-LCIMTextMessage messageWithCity = new LCIMTextMessage("It is getting cold.");
-messageWithCity["city"] = "Montreal";
-```
-
-```java
-LCIMTextMessage messageWithCity = new LCIMTextMessage();
-messageWithCity.setText("It is getting cold.");
-HashMap attr = new HashMap();
-attr.put("city", "Montreal");
-messageWithCity.setAttrs(attr);
-```
-
-```objc
-NSDictionary *attributes = @{ @"city": @"Montreal" };
-LCIMTextMessage *messageWithCity = [LCIMTextMessage messageWithText:@"It is getting cold." attributes:attributes];
-```
-
-```js
-var messageWithCity = new TextMessage("It is getting cold.");
-messageWithCity.setAttributes({ city: "Montreal" });
-```
-
-```swift
-let messageWithCity = IMTextMessage(text: "It is getting cold.")
-messageWithCity.attributes = ["city": "Montreal"];
-```
-
-```dart
-TextMessage message = TextMessage();
-message.text = 'It is getting cold.';
-message.attributes = {'city': 'Montreal'};
-```
-
-
-
-### Creating Your Own Message Types
-
-If the built-in types cannot fulfill your requirements at all, you can implement your custom message types.
-
-
-<>
-
-By inheriting from `LCIMTypedMessage`, you can define your own types of messages. The basic steps include:
-
-- Define a subclass inherited from `LCIMTypedMessage`.
-- Register the subclass when initializing.
-
-```cs
-class EmojiMessage : LCIMTypedMessage {
- public const int EmojiMessageType = 1;
-
- public override int MessageType => EmojiMessageType;
-
- public string Ecode {
- get {
- return data["ecode"] as string;
- } set {
- data["ecode"] = value;
- }
- }
-}
-
-// Register subclass
-LCIMTypedMessage.Register(EmojiMessage.EmojiMessageType, () => new EmojiMessage());
-```
-
->
-<>
-
-By inheriting from `LCIMTypedMessage`, you can define your own types of messages. The basic steps include:
-
-- Implement the new message type inherited from `LCIMTypedMessage`. Make sure to:
- - Add the `@LCIMMessageType(type=123)` annotation to the class The value assigned to the type (`123` here) can be defined yourself. Negative numbers are for types offered by default and positive numbers are for those defined by you.
- - Add the `@LCIMMessageField(name="")` annotation when declaring custom fields `name` is optional. Custom fields need to have their getters and setters.
- - **Include an empty constructor** (see the sample below), otherwise there will be an error with type conversion.
-- Call `LCIMMessageManager.registerLCIMMessageType()` to register the class.
-- Call `LCIMMessageManager.registerMessageHandler()` to register the handler for messages.
-
-```java
-@LCIMMessageType(type = 123)
-public class CustomMessage extends LCIMTypedMessage {
- // An empty constructor has to be present
- public CustomMessage() {
-
- }
-
- @LCIMMessageField(name = "_lctext")
- String text;
- @LCIMMessageField(name = "_lcattrs")
- Map attrs;
-
- public String getText() {
- return this.text;
- }
-
- public void setText(String text) {
- this.text = text;
- }
-
- public Map getAttrs() {
- return this.attrs;
- }
-
- public void setAttrs(Map attr) {
- this.attrs = attr;
- }
-}
-
-// Register
-LCIMMessageManager.registerLCIMMessageType(CustomMessage.class);
-```
-
->
-<>
-
-By inheriting from `LCIMTypedMessage`, you can define your own types of messages. The basic steps include:
-
-- Implement the `LCIMTypedMessageSubclassing` protocol;
-- Register the subclass. This is often done by calling `[YourClass registerSubclass]` in the `+load` method of the subclass or `-application:didFinishLaunchingWithOptions:` in `UIApplication`.
-
-```objc
-// Definition
-
-@interface CustomMessage : LCIMTypedMessage
-
-+ (LCIMMessageMediaType)classMediaType;
-
-@end
-
-@implementation CustomMessage
-
-+ (LCIMMessageMediaType)classMediaType {
- return 123;
-}
-
-@end
-
-// Register subclass
-- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- [CustomMessage registerSubclass];
-}
-```
-
->
-<>
-
-By inheriting from `TypedMessage`, you can define your own types of messages. The basic steps include:
-
-- Declare the new message type by inheriting from `TypedMessage` or its subclass and then:
- - Apply the `messageType(123)` decorator to the class. The value assigned to the type (`123` here) can be defined yourself (negative numbers are for types offered by default and positive numbers are for those defined by you).
- - Apply the `messageField(['fieldName'])` decorator to the class to declare the fields that should be sent.
-- Call `Realtime#register()` to register the type.
-
-For example, to implement the `OperationMessage` introduced in [Transient Messages](#transient-messages):
-
-```js
-// TypedMessage, messageType, and messageField are provided by leancloud-realtime
-// Use `var { TypedMessage, messageType, messageField } = AV;` in browser
-var { TypedMessage, messageType, messageField } = require("leancloud-realtime");
-// Define OperationMessage for sending and receiving messages regarding user operations
-export class OperationMessage extends TypedMessage {}
-// Specify the type; can be other positive integers
-messageType(1)(OperationMessage);
-// The `op` field needs to be sent with the message
-messageField("op")(OperationMessage);
-// Register the class, otherwise an incoming OperationMessage cannot be automatically resolved
-realtime.register(OperationMessage);
-```
-
->
-<>
-
-By inheriting from `IMCategorizedMessage`, you can define your own types of messages. The basic steps include:
-
-- Implement the `IMMessageCategorizing` protocol;
-- Register the subclass. This is often done by calling `try CustomMessage.register()` in the `application(_:didFinishLaunchingWithOptions:)` method of `AppDelegate`.
-
-```swift
-// Specify the CustomMessage class
-class CustomMessage: IMCategorizedMessage {
-
- // Specify the type; can be other positive integers
- class override var messageType: MessageType {
- return 1
- }
-}
-
-// Register the message type
-func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
-
- do {
- try CustomMessage.register()
- } catch {
- print(error)
- return false
- }
-
- return true
-}
-```
-
->
-<>
-
-You can define your own message type with a subclass inherited from `TypedMessage`:
-
-```dart
-// Custom message type CustomMessage
-class CustomMessage extends TypedMessage {
- @override
-
- int get type => 123;
- CustomMessage() : super();
- CustomMessage.from({
- @required String text,
- //...
- }) {
- this.text = text;
- }
-}
-TypedMessage.register(() => CustomMessage());
-```
-
->
-
-
-See [Back to Receiving Messages](/sdk/im/guide/beginner/) in the previous chapter for more details on how to receive messages with custom types.
-
-## Continue Reading
-
-- [3. Security, Chat Rooms, and Temporary Conversations](/sdk/im/guide/senior/)
-- [4. Hooks and System Conversations](/sdk/im/guide/systemconv/)
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/overview.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/overview.mdx
deleted file mode 100644
index d2850dde0..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/overview.mdx
+++ /dev/null
@@ -1,306 +0,0 @@
----
-title: Instant Messaging Overview
-sidebar_label: Overview
-sidebar_position: 0
----
-
-Instant Messaging allows you to quickly implement functions like instant messaging and data synchronization in your app. It is designed with the following goals:
-
-- **Easily add messaging functions to existing apps**
-
- Many of our customers already have their products running smoothly and the only thing they need is to add instant messaging as a bonus feature in their apps. With this in mind, Instant Messaging is designed to work independently without interfering with your existing account systems.
-
- We offer a full collection of UI frameworks and SDKs for you to design and develop messaging functions for a variety of platforms and scenarios.
-
-- **Provide extensibility for custom features**
-
- Instant Messaging supports common types of messages including text messages, images, audios, videos, locations, and binary data. You can define your own types of messages and build UI for them if you need. You can also implement functions like recalling messages, editing messages, mentioning people, transient messages, delivery receipts, push notifications, and text moderation. If you want to build chat rooms that could hold infinite numbers of users, or system conversations that could be used for chatbots, Instant Messaging also got you covered.
-
-- **Enhance security with permission management**
-
- When you use Instant Messaging, clients and servers use full-duplex communication channels through WebSocket with TLS encrypted transmissions. This ensures that conversations happening within your app cannot be fetched by any unauthorized parties. To further control user activities, you can use our third-party signing mechanism to verify all the sensitive operations before they can be processed.
-
-- **Lower costs for maintenance**
-
- With our 24/7 technical support backed by experienced engineers, you can be confident that you are able to integrate our services into your projects with a minimal amount of hassle. Besides this, you are also freed from handling all the errors that might happen to software and infrastructure, as well as upgrading servers when the usage of your app grows quickly.
-
-Instant Messaging is already widely used in scenarios like in-app socializing, remote collaboration, customer service, live streaming, and game state synchronization.
-
-## Features
-
-Here are the main features offered by Instant Messaging:
-
-- **Basic chatting**
-
- - Besides one-on-one chats and group chats, we also offer **open chat rooms** that could hold infinite numbers of people, which can be used for scenarios like live streaming and open courses that demand massive discussions in a group. There are also **system conversations** that could interact with users spontaneously, as well as **temporary conversations** which work best if you need to build customer service systems. All these functions share the same programming interface.
- - Users can send all kinds of messages, including text messages, images, audios, videos, locations, and **binary data**. You can develop your own types of messages if you need.
- - History messages are automatically saved on the cloud, which can be retrieved in multiple ways.
-
-- **Advanced messaging features**
-
- - Mentioning people with **"@"**
- - **Recalling and editing** messages
- - **Getting receipts** when messages are delivered and read
- - **Muting** conversations
- - **Sending status updates** like "Someone is typing…"
- - Converting messages to **push notifications** when receivers are offline
- - Setting **priorities** for messages in chat rooms so that important messages can get quicker delivery
-
-- **Multi-device sign-on and message synchronization**
-
- It is becoming a common requirement that users can have their accounts logged in on multiple devices at the same time. Here we allow you to make your own decision whether users can do so and receive messages on all their devices, or they can only **log in on a single device** at a time.
- What if a user accidentally loses their connection to the Internet? No worries. Our **synchronization mechanism** guarantees that unreceived messages can always be synchronized to users' devices once the connection is re-established.
-
-- **Group management**
-
- The messages sent through all the conversations can be filtered upon a list of keywords you define or through a plugin you build. This helps your apps comply with the laws and regulations in the areas they are operated.
-
-- **Access control**
-
- For any user that wants to send and receive messages, the only thing they need is a `clientId` identifying them. By decoupling with the account system of the app, it is made easy for you to add Instant Messaging into your app and it also helps us better focus on our role as a "messenger".
- We provide a **third-party signing mechanism** that allows you to verify all the operations performed within the app with your own server. This ensures that all the requests sent to the server are legitimate.
- Our SDKs and the cloud use full-duplex communication channels through WebSocket with TLS encrypted transmissions, which further ensures the security of users' conversations.
-
-- **Customizable components**
-
- We offer a number of common features that you can directly use, while you also have the freedom to build your own logic for your special needs:
-
- - You can verify operations like creating/joining/leaving conversations and retrieving history messages by connecting to your existing account system via our third-party signing mechanism.
- - You can attach **hooks** to different stages of a message delivery process to implement your custom logic like filtering out certain keywords or customizing push notifications.
- - You can use **webhooks** to implement message synchronization between the cloud and your app's backend.
- - Besides client-side SDKs, we also offer REST APIs for you to implement functions under trusted environments.
-
- Our flexibility and extensibility give you the power to add more fun to your app besides the basic chatting functions.
-
-## Cross-Platform Support
-
-We offer client-side SDKs for a variety of mainstream platforms. Feel free to take a look at their source code on our [GitHub](https://github.com/leancloud). You are welcome to talk to us if you have any questions or needs.
-
-You may also try out our demos to quickly familiarize yourself with our services. You can find them on **Download > Demos**.
-
-## Terminologies
-
-The concepts mentioned below will be used frequently in our documentation. It would be helpful if you could familiarize yourself with them.
-
-### `clientId`, User, and Log in
-
-In Instant Messaging, each terminal is called a "client". Each client has a unique ID (`clientId`) that marks itself within an app. Such an ID can be generated by your app, but it has to be **a string of alphanumeric characters, underscores, and hyphens that is not longer than 64 characters and does not begin with a numeric character**. In most cases, each client matches an actual user of your app, but it does not mean that only users can act as clients. A probe can also be a client that keeps broadcasting the data collected by it to other people.
-
-To start using Instant Messaging, each client needs to build a WebSocket connection to the cloud and register itself with a unique `clientId`. Such a process is called "logging in". Keep in mind that this is not the same as the log-in process of the app itself.
-
-By default, a `clientId` can log in on multiple devices, and multiple `clientId`s can log in on the same device as well. If you want each user to be logged in on only one device at a time, you can specify a "tag" when logging in so that when duplicated tags are detected on the cloud, the devices that are already logged in will be logged out. You can decide whether to use tags or not based on your actual needs.
-
-After logging in, our SDK will ensure that the connection is always alive and will automatically reconnect if the network status ever changes. The connection will be ended once the app goes into the background and push notifications will be used for message delivery.
-
-### Conversation
-
-When a user starts chatting with someone after logging in, a `Conversation` would be formed. In Instant Messaging, a conversation contains all the members belonging to it and holds all the messages sent by them, so whenever a client sends a message, it is always sent into a conversation. Before a client could send messages to others, it has to create or join a conversation first and invite other people to it (optional). Only those who are inside a conversation can have access to the messages in it.
-
-When a conversation is created, a record would be added to the `_Conversation` table which can be found on **Developer Center > Your game > Game Services > Cloud Services > Data Storage > Data**. Each conversation holds properties like group name, members, as well as custom attributes. The table below shows the relationship between the properties of a conversation and the fields of the `_Conversation` table:
-
-| Property | Table Field | Type | Constraint | Description |
-| ---------------- | ----------- | --------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `name` | `name` | `String` | Optional | The name of the group chat. |
-| `attributes` | `attr` | `Object` | Optional | Custom attributes. |
-| `conversationId` | `objectId` | `String` | | A unique ID generated by the cloud (read-only). |
-| `creator` | `c` | `String` | | The `clientId` of the creator (read-only). |
-| `members` | `m` | `Array` | | All the members of a basic conversation (not applicable for chat rooms or system conversations). |
-| `mute` | `mu` | `Array` | | Members who muted the conversation. These members will not receive push notifications. |
-| `lastMessageAt` | `lm` | `Date` | | The time the last message is sent. |
-| `transient` | `tr` | `Boolean` | Optional | Whether it is a chat room. |
-| `system` | `sys` | `Boolean` | Optional | Whether it is a system conversation. |
-| `unique` | `unique` | `Boolean` | Optional | If this is `true`, the same conversation will be reused when a new conversation is created with the same composition of members and `unique` to be `true`. |
-
-We do have different types of conversations designed for different scenarios. All of them will be stored in `_Conversation` regardless of their types.
-
-#### Common Scenarios
-
-Let's first go through some common scenarios where Instant Messaging could be used.
-
-- **One-on-one chats**
-
- This is the conversation between two clients and you can decide whether other users can find it or not (usually it is private). It will be converted to a group chat if new clients are added to it and, again, you can decide if all the members will stay in the same conversation or form a new one.
-
-- **Group chats**
-
- A group chat contains two or more clients and oftentimes members can be added or removed at any time. A name is usually assigned to a group chat, such as "Family", "Friends", or "Co-workers". It is possible for a group chat to have only two or even one member, but it does not make any difference on whether it is considered a group chat or not. You can decide if group chats are public (such as searchable by name) or not.
-
-- **Chat rooms**
-
- A chat room is similar to a group chat since both of them involve a lot of people. What makes a chat room different is that the number of people in it is often way higher than that of a group chat. In such a situation, the number of people in a chat room becomes more meaningful than the specific list of people in it. There is also a difference in how members of a conversation are managed. By opening a chat room, a client joins it, and by closing it, the client leaves it. Therefore, message synchronization and push notifications are disabled for chat rooms.
-
-- **Official accounts and bots**
-
- You can build official accounts in your app and have them broadcast messages to the users who subscribe to them or chat with specific users. You can also implement bots that can automatically reply to messages sent from users.
-
-- **Customer service**
-
- Customers can start temporary conversations with representatives to discuss solutions to their problems. These conversations can be closed once the problems are solved.
-
-Instant Messaging offers the following four types of conversations to cover all these scenarios.
-
-#### Basic Conversation
-
-This is the most commonly used type of conversation that could serve one-on-one chats and group chats. It offers the following functions:
-
-- Send and receive messages among members of it
-- Add and remove members (500 maximum) with all the existing members notified
-- Find out who are online
-- Mention members, recall messages, edit messages, send transient messages, get receipts when messages are delivered and read, send will messages, and receive push notifications
-- Receive push notifications when offline and synchronize unreceived messages when online
-- Store message history on the cloud and perform various types of queries on it
-
-Tips: You may assign properties to a conversation to mark if it is a one-on-one chat or a group chat. You may also mark if a conversation is private or public. Such properties can be stored in `Conversation.attributes`.
-
-#### Chat Room
-
-This type of conversation is dedicated to chat rooms (it has `tr` to be `true` in `_Conversation`). Similar to a basic conversation, users can create, join, or leave it at any time and all the messages will be stored on the cloud. It is different from a basic conversation on the following basis:
-
-- **No limit on the number of people**; does not have a fixed list of members; members are removed once they are offline (field `m` is ignored)
-- **The number of members** can be retrieved instead of the specific list of members
-- No offline messages, push notifications, or delivery receipts
-- No notifications for members joining or leaving
-- Cannot invite or remove people
-- A user can only be in one chat room at each time and will automatically leave the previous one when joining a new one
-- A user has to rejoin a chat room if it becomes offline for more than 30 minutes
-
-Tips: Although a chat room does not limit the number of people in it, the user experience can be impaired when there are too many people sending a lot of messages. We suggest that you limit the number of people in each chat room to less than **5,000** and split big chat rooms into smaller ones if possible.
-
-#### System Conversation
-
-This type of conversation can be used to build bots, official accounts, service accounts, and in-app notifications (it has `sys` to be `true` in `_Conversation`). It has the following traits:
-
-- A user subscribes to it by joining and unsubscribes by leaving (field `m` is ignored)
-- Conversations can only be created on the server side and clients can only subscribe or unsubscribe to the existing conversations
-- Messages can be sent to either all subscribers or specific users
-- The messages sent from users will be stored in the `_SysMessage` table and cannot be seen by other users
-- You can set up hooks to receive messages from users and reply to them with REST API
-
-#### Temporary Conversation
-
-This type of conversation does not get stored in the `_Conversation` table. It is meant for special scenarios with:
-
-- Short TTL
-- Fewer members (10 clients maximum)
-- No message history needed
-
-We recommend temporary conversation to be used for customer service. It has the following traits:
-
-- Cannot mute or unmute
-- Cannot update attributes
-- Allow message operations and querying members just like basic conversations
-
-Tips: Temporary conversations last shorter and do not get stored in the `_Conversation` table. This **reduces the size of data stored in your app** and **lowers the cost needed** as well.
-
-#### Summary
-
-| Type of Conversation | Scenarios | Member Management | Message Operations | Message History |
-| -------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | --------------- |
-| **Basic Conversation** | One-on-one chats and group chats | Members are persisted (500 members maximum) | Only members can send or receive messages | Supported |
-| **Chat Room** | Chat rooms, barrage, and real-time comments | Members are not persisted; Users cannot be invited; No limit on the number of people | Everyone can send messages; Online users can receive messages | Supported |
-| **System Conversation** | Official accounts, bots, system notifications, and custom messages | Does not have the concept of members; A list of subscribers can be maintained; No limit on the number of subscribers | Messages can be sent to subscribers through API; Webhooks can be set up to handle messages | Supported |
-| **Temporary Conversation** | Customer service | Fixed members | Only members can send or receive messages | Not supported |
-
-### Message
-
-A message is a basic unit for transmitting data within a conversation. Each message can hold up to **5 KB** of data in the format of text. There is no requirement on how the text should be formatted and you are free to define your own types of messages based on it.
-
-When sending a message, a parameter can be specified to make it either a normal message or a transient message. A normal message comes with features like delivery receipts, persistent storage, and push notifications, while a transient message cannot be saved, received later, or pushed to offline users. Therefore, status updates like "Someone is typing…" are best sent as transient messages and messages composed by users shall be sent as normal messages.
-
-Our service ensures that a normal message can be delivered at least once. At the same time, our SDKs are able to filter out duplicate messages. We offer both SDKs and REST API for you to send messages: SDKs are often for clients and REST API is for sending messages from the server side. When using REST API, you can specify the sender of a message and the ID of the target conversation, as well as the receivers of the message if it is sent to a system conversation.
-
-#### Default Types for Rich Media Messages
-
-We offer a number of predefined JSON-based types (`TypedMessage`) for rich media messaging, including:
-
-- Text (`TextMessage`)
-- Image (`ImageMessage`)
-- Audio (`AudioMessage`)
-- Video (`VideoMessage`)
-- Location (`LocationMessage`)
-
-The image below shows the inheritance relationships among them:
-
-![TypedMessage is inherited from Message. TextMessage, ImageMessage, AudioMessage, VideoMessage, LocationMessage, and other types of messages are inherited from TypedMessage.](/img/realtime_v2_message_types.svg)
-
-As mentioned above, rich media messages are based on the JSON format. Therefore, they need to be serialized to a JSON string containing the following properties when sent via the REST API.
-Client-side SDKs will do the conversion automatically when sending messages.
-
-| Property | Constraint | Description |
-| ---------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `_lctype` | | Type of the rich media message
Message
Type
Text message
-1
Image message
-2
Audio message
-3
Video message
-4
Location message
-5
File message
-6
All the types above use negative numbers. Positive numbers are reserved for custom types, and zero is reserved for "no type". |
-| `_lctext` | | Text description of the rich media message |
-| `_lcattrs` | | A JSON string containing custom attributes |
-| `_lcfile` | | If the message contains a file (image, audio, video, or generic file), `_lcfile` would contain the information about that file. |
-| `url` | | The URL of the file after it is uploaded. Note that the URL in historical messages will not be updated when you bind or re-bind a custom domain name. |
-| `objId` | Optional | The objectId of the file in the `_File` class. |
-| `metaData` | Optional | File metadata. |
-
-The above properties are common to all types of rich media messages.
-
-You can easily extend your own message types based on our framework.
-
-#### Advanced Features
-
-As mentioned earlier, we offer the following functions besides basic chatting:
-
-- Mentioning people with "@"
-- Recalling and editing messages
-- Text moderation
-- Getting receipts when messages are delivered and read
-- Muting conversations
-- Sending status updates like "Someone is typing…"
-- Converting messages to push notifications when receivers are offline
-- Setting priorities for messages in chat rooms so that important messages can get prioritized delivery
-
-You can read [Advanced Messaging Features](/sdk/im/guide/intermediate/) and [Text Moderation](/sdk/im/guide/senior/) to learn more about them.
-
-## Restrictions
-
-- In **every minute**, a client can send **at most 60 messages**, query history messages **at most 120 times**, and perform operations like joining conversations, leaving conversations, logging in, and logging out **at most 30 times**. Incoming requests will be rejected if the limits are exceeded (the callbacks implemented with SDK will not be triggered). If you are performing these operations with REST API, the limits will not apply.
-- For each app, at most 160,000 messages can be delivered to all the clients each second. The messages exceeding the limit will be discarded. Please contact us if your app needs a higher quota.
-- The size of each message (including metadata such as `pushData`) shall be less than or equal to 5 KB.
-- Each conversation can hold at most 500 people. If you add more than 500 IDs into the `m` field with the Data Storage API, only the first 500 IDs will be used.
-- The same ID is not supposed to be logged in on too many devices. If we detect that an ID is logged in on more than 5 IP addresses at the same time, this ID will be billed with each IP as an independent user on that day.
-- If a user has more than 50 conversations containing unread messages, the cloud will **randomly** pick 50 of them and deliver their unread messages (or the amounts of them) to the user when the user logs in. The undelivered messages will not be lost but need to be manually retrieved.
-- If a user has a conversation containing more than 100 offline messages, the former messages will not be automatically delivered when the user logs in and the user will not see the amount of them. The undelivered messages can be manually retrieved.
-- Updating the file URL (**Developer Center > Your game > Game Services > Cloud Services > Data Storage > File > Settings**) will not automatically refresh the URLs in the existing messages (including rich media messages).
-- There are request frequency and quantity limitations on invoking Instant Messaging-related REST APIs. See [Instant Messaging REST API Guide](/sdk/im/guide/rest/) for details.
-
-### Lifespan of Conversations
-
-If no message is sent to a conversation (basic conversation, chat room, or system conversation) in the **past 6 months** through either SDK or REST API, or none of its fields in the `_Conversation` table are updated, the conversation will be considered **inactive** and will be deleted automatically. Keep in mind that querying messages does not update the `_Conversation` table, so a conversation that only gets queried will also be seen as inactive.
-
-If you attempt to send a message to a deleted conversation through either SDK or REST API, the error `4401 INVALID_MESSAGING_TARGET` will be returned which means that the conversation does not exist anymore. The messages associated with deleted conversations will also be gone.
-
-Active conversations will never get deleted.
-
-### Lifespan of Messages
-
-A message will be stored on the cloud for **6 months**. This means that you can only query the message history of a conversation in the past 6 months. If you would like to pay to extend the period, please reach out to us.
-You may also synchronize the message history to your own server with REST API.
-
-## Hooks
-
-See [Hooks](/sdk/im/guide/systemconv/).
-
-## Price
-
-Charged by active users, up to 500 users for free each day, for overage: 1 USD / 10,000 users per day.
-
-* REST API charges by number of requests. Same as Data Storage: first 30,000 times for free each day, for overage: 0.04 USD / 1000 times.
-* A file fee is charged for multimedia messages using the file service. Please refer to the price page of the [official website](https://developer.taptap.io/product-intro/price).
-* All requests to the '_Conversation' table incur a data storage charge. The price is the same as Data Storage.
-
-## Guides
-
-Basic features:
-
-- [1. Basic Conversations and Messages](/sdk/im/guide/beginner/)
-- [2. Advanced Messaging Features, Push Notifications, Synchronization, and Multi-Device Sign-on](/sdk/im/guide/intermediate/)
-- [3. Security, Chat Rooms, and Temporary Conversations](/sdk/im/guide/senior/)
-- [4. Hooks and System Conversations](/sdk/im/guide/systemconv/)
-
-REST API:
-
-- [Instant Messaging REST API](/sdk/im/guide/rest/)
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/rest.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/rest.mdx
deleted file mode 100644
index def1989f7..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/rest.mdx
+++ /dev/null
@@ -1,1746 +0,0 @@
----
-title: Instant Messaging REST API
-sidebar_position: 5
----
-
-## Overview
-
-The base URL for sending requests can be found on **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings**.
-For POST and PUT requests, the request body must be a JSON object and the Content-Type in the HTTP Header should be `application/json`.
-Requests are authenticated according to the key-value pairs included in the HTTP Header. See [Data Storage REST API](/sdk/storage/guide/rest/) for more information.
-
-The `_Conversation` table includes some built-in fields that denote a conversation’s attributes and members. One-on-one chats, group chats, chat rooms, and system conversations are all stored in this table. See [Instant Messaging Overview](/sdk/im/guide/overview/) for more information.
-To avoid inconsistencies, we strongly discourage you to manipulate the data in the `_Conversation` table with the Data Storage API.
-
-The current API version is `1.2`:
-
-- APIs related to one-on-one chats and group chats start with `rtm/conversations`.
-- APIs related to chat rooms start with `rtm/chatrooms`. In the `_Conversation` table, chat rooms have their `tr` field being `true`.
-- APIs related to system conversations start with `rtm/service-conversations`. In the `_Conversation` table, system conversations have their `sys` field being `true`.
-
-APIs related to clients start with `rtm/clients`.
-APIs used for global operations start with `rtm/{function}`. For example, `rtm/all-conversations` can be used to look up all conversations regardless of their types.
-
-## One-On-One Chats and Group Chats
-
-### Creating Conversations
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"name":"My First Conversation", "m": ["BillGates", "SteveJobs"], "unique": true}' \
- https://{{host}}/1.2/rtm/conversations
-```
-
-Above is a minimal example of creating a conversation. The conversation contains two members with their client IDs being BillGates and SteveJobs. The objectId of the conversation will be returned once the conversation is created, after which the client will be able to send messages with the ID. The newly created conversation can be found in the `_Conversation` table.
-See [Instant Messaging Overview](/sdk/im/guide/overview/) for more information about the attributes of a conversation.
-To ensure that the conversation is unique, you can provide the `"unique": true` parameter.
-
-The response looks like
-
-```json
-{
- "unique": true,
- "updatedAt": "2020-05-26T06:42:31.492Z",
- "name": "My First Conversation",
- "objectId": "5eccba570d3a42c5fd4e25c3",
- "m": ["BillGates", "SteveJobs"],
- "createdAt": "2020-05-26T06:42:31.482Z",
- "uniqueId": "6c7b0e5afcae9aa1139a0afa25833dec"
-}
-```
-
-Keep in mind that the only difference between group chats and one-on-one chats is that they have different numbers of clients. The same API is used for both types of conversations.
-
-### Retrieving Conversations
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -G \
- --data-urlencode 'where={"name": "first conversation"}' \
- --data-urlencode 'skip=1' \
- --data-urlencode 'limit=20' \
- https://{{host}}/1.2/rtm/conversations
-```
-
-| Parameter | Constraint | Description |
-| ----- | ---- | ------------------------------------------------------------------------ |
-| skip | Optional |
-| limit | Optional | Can be used for pagination when used together with `skip` |
-| where | Optional | See [Data Storage REST API](/sdk/storage/guide/rest/) for more information |
-
-The response looks like
-
-```json
-{
- "results": [
- {
- "name": "test conv1",
- "m": ["tom", "jerry"],
- "createdAt": "2018-01-17T04:15:33.386Z",
- "updatedAt": "2018-01-17T04:15:33.386Z",
- "objectId": "5a5ecde6c3422b738c8779d7"
- }
- ]
-}
-```
-
-### Updating Conversations
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"name":"Updated Conversation"}' \
- https://{{host}}/1.2/rtm/conversations/{conv_id}
-```
-
-All attributes of the `_Conversation` table except `m` can be updated with this interface.
-
-The response looks like
-
-```json
-{
- "updatedAt": "2018-01-16T03:40:37.683Z",
- "objectId": "5a5d7433c3422b31ed845e76"
-}
-```
-
-### Deleting Conversations
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/conversations/{conv_id}
-```
-
-The response looks like
-
-```json
-{}
-```
-
-### Adding Members
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"client_ids": ["Tom", "Jerry"]}' \
- https://{{host}}/1.2/rtm/conversations/{conv_id}/members
-```
-
-The response looks like
-
-```json
-{
- "updatedAt": "2018-01-16T03:40:37.683Z",
- "objectId": "5a5d7433c3422b31ed845e76"
-}
-```
-
-### Removing Members
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"client_ids": ["Tom", "Jerry"]}' \
- https://{{host}}/1.2/rtm/conversations/{conv_id}/members
-```
-
-The response looks like
-
-```json
-{
- "updatedAt": "2018-01-16T03:40:37.683Z",
- "objectId": "5a5d7433c3422b31ed845e76"
-}
-```
-
-### Retrieving Members
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/conversations/{conv_id}/members
-```
-
-The response looks like
-
-```json
-{ "result": ["client1", "client2"] }
-```
-
-### Adding Members That Mute a Conversation
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"client_ids": ["Tom", "Jerry"]}' \
- https://{{host}}/1.2/rtm/conversations/{conv_id}/mutes
-```
-
-| Parameter | Description |
-| ---------- | -------------------------- |
-| client_ids | An array of `Client ID`s that mute the conversation |
-
-The response looks like
-
-```json
-{
- "updatedAt": "2018-01-16T03:40:37.683Z",
- "objectId": "5a5d7433c3422b31ed845e76"
-}
-```
-
-### Removing Members That Mute a Conversation
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"client_ids": ["Tom", "Jerry"]}' \
- https://{{host}}/1.2/rtm/conversations/{conv_id}/mutes
-```
-
-The response looks like
-
-```json
-{
- "updatedAt": "2018-01-16T03:40:37.683Z",
- "objectId": "5a5d7433c3422b31ed845e76"
-}
-```
-
-### Retrieving Members That Muted a Conversation
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/conversations/{conv_id}/mutes
-```
-
-The response looks like
-
-```json
-{ "result": ["client1", "client2"] }
-```
-
-### One-On-One Chats and Group Chats: Sending Messages
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "", "message": ""}' \
- https://{{host}}/1.2/rtm/conversations/{conv_id}/messages
-```
-
-**Attention**: Due to the administrative essence of this interface, when you send messages with this interface, our system won’t check if the **from_client** has the permission to send messages to the conversation.
-If you are using rich media messages in your application, please make sure to have the **message** field follow the required format.
-
-| Parameter | Constraint | Description |
-| ------------------ | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| from_client | Required | The client ID of the sender. |
-| message | Required | The content of the message. Although the value of this field is a string, there is no limit on the format of the string. Therefore, you can define messages with different formats using this field, as long as the size of the value of this field doesn’t exceed the limit of 5 KB. |
-| transient | Optional | Whether the message is transient. Defaults to `false`. |
-| no_sync | Optional | By default, the message will be synced to the client of the `from_client` that is online. You can disable this feature by setting this property to `true`. |
-| push_data | Optional | The content used for push notifications. If the receiver uses an iOS device and is not online, the value of this property will be used for sending push notifications. See [2. Advanced Messaging Features, Push Notifications, Synchronization, and Multi-Device Sign-on](/sdk/im/guide/intermediate/) for more information. |
-| priority | Optional | The priority of the message. Could be `high`, `normal`, or `low`. The value is case-insensitive. Defaults to `normal`. This parameter is only valid if the message is transient or is sent to a chat room. When there is high traffic on the server or users’ devices, messages with high priorities will still be queued. |
-| mention_all | Optional | Boolean. Used to remind all the members in the conversation to pay attention to this message. |
-| mention_client_ids | Optional | Array. Includes the list of `client_id` that will be reminded to pay attention to this message. Can contain at most 20 client IDs. |
-
-Response:
-
-By default, messages are sent asynchronously with the API. You will receive the ID of the message along with the server timestamp, like `{"msg-id":"qNkRkFWOeSqP65S9fDyHJw", "timestamp":1495431811151}`.
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### Retrieving History Messages
-
-This interface can only be accessed with the master key.
-To ensure the security of history messages, you can enable the signing mechanism. See [3. Security, Permission Management, Chat Rooms, and Temporary Conversations](/sdk/im/guide/senior/) for more information.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- https://{{host}}/1.2/rtm/conversations/{conv_id}/messages
-```
-
-| Parameter | Constraint | Description |
-| -------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------ |
-| msgid | Optional | The ID of the starting message. **When this parameter is provided, the `timestamp` of the corresponding message must be provided as well.** |
-| timestamp | Optional | The start timestamp (in milliseconds). Defaults to the current time. |
-| till_msgid | Optional | The ID of the ending message. **When this parameter is provided, the `till_timestamp` of the corresponding message must be provided as well.** |
-| till_timestamp | Optional | The end timestamp (in milliseconds). Defaults to 0. |
-| include_start | Optional | Whether to include the message determined by `timestamp` and `msgid`. Boolean. Defaults to `false`. |
-| include_stop | Optional | Whether to include the message determined by `till_timestamp` and `till_msgid`. Boolean. Defaults to `false`. |
-| reversed | Optional | Sort the results in reverse of the default order (time order). With this option enabled, `till_timestamp` defaults to the current time and `timestamp` defaults to 0. Boolean. Defaults to `false`. |
-| limit | Optional | Used to limit the number of records returned. Defaults to 100. Can be no more than 1000. |
-| client_id | Optional | Viewer ID (used for signature). |
-| nonce | Optional | Nonce for the signature (used for signature). |
-| signature_ts | Optional | Timestamp for the signature (used for signature) in milliseconds. |
-| signature | Optional | The signature (used for signature). |
-
-This interface contains a lot of parameters related to time. To make things clear, here is an example. Assuming that a conversation has 3 messages with their IDs being `id1`, `id2`, and `id3` and timestamps being `t1`, `t2`, and `t3` (`t1` < `t2` < `t3`). The table below shows the results for queries with different parameters (blank cells indicate that the default values are used):
-
-| timestamp | msgid | till_timestamp | till_msgid | include_start | include_stop | reversed | Result |
-| --------- | ----- | -------------- | ---------- | ------------- | ------------ | -------- | ------- |
-| t3 | id3 | t1 | id1 | | | | id2 |
-| t3 | id3 | t1 | id1 | true | | | id3 id2 |
-| t3 | id3 | t1 | id1 | | true | | id2 id1 |
-| t1 | id1 | t3 | id3 | | | true | id2 |
-| t1 | id1 | t3 | id3 | true | | true | id1 id2 |
-| t1 | id1 | t3 | id3 | | true | true | id2 id3 |
-
-The response would be a JSON array containing messages sorted from the newest to the oldest. If `reversed` is enabled, the messages will be sorted in reverse order.
-
-Response:
-
-```json
-[
- {
- "timestamp": 1408008498571,
- "conv-id": "219946ef32e40c515d33ae6975a5c593",
- "data": "Nice weather!",
- "from": "u111872755_9d0461adf9c267ae263b3742c60fa",
- "msg-id": "vdkGm4dtRNmhQ5gqUTFBiA",
- "is-conv": true,
- "is-room": false,
- "to": "5541c02ce4b0f83f4d44414e",
- "bin": false,
- "from-ip": "202.117.15.217"
- }
- // ...
-]
-```
-
-To look up all the messages sent by a user, use `GET /rtm/clients/{client_id}/messages`.
-To look up all the messages sent through your application, use `GET /rtm/messages`.
-
-### One-On-One Chats and Group Chats: Updating Messages
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "", "message": "", "timestamp": 123}' \
- https://{{host}}/1.2/rtm/conversations/{conv_id}/messages/{message_id}
-```
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ---------------------- |
-| from_client | Required | The client ID of the sender |
-| message | Required | The message |
-| timestamp | Required | The timestamp of the message |
-
-If successful, the `200 OK` status code will be returned.
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### One-On-One Chats and Group Chats: Recalling Messages
-
-This interface can only be accessed with the master key. SDK support is needed for requests sent to this interface to take effect. Refer to the interface for updating messages for more information.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "", "timestamp": 123}' \
- https://{{host}}/1.2/rtm/conversations/{conv_id}/messages/{message_id}/recall
-```
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ---------------------- |
-| from_client | Required | The client ID of the sender |
-| timestamp | Required | The timestamp of the message |
-
-If successful, the `200 OK` status code will be returned.
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### Deleting Messages
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -G \
- --data-urlencode 'from_client=some-client-id' \
- --data-urlencode 'timestamp=123' \
- https://{{host}}/1.2/rtm/conversations/{conv_id}/messages/{message_id}
-```
-
-Keep in mind that this interface will only delete messages on the server and won’t affect clients.
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ---------------------- |
-| from_client | Required | The client ID of the sender |
-| timestamp | Required | The timestamp of the message |
-
-Response:
-
-```json
-{}
-```
-
-## Chat Rooms
-
-### Creating Chat Rooms
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"name":"My First Chatroom"}' \
- https://{{host}}/1.2/rtm/chatrooms
-```
-
-See [Instant Messaging Overview](/sdk/im/guide/overview/) for more information about the attributes of a conversation.
-
-The response looks like
-
-```json
-{
- "objectId": "5a5d7432c3422b31ed845e75",
- "createdAt": "2018-01-16T03:40:32.814Z"
-}
-```
-
-### Retrieving Chat Rooms
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -G \
- --data-urlencode 'where={"name": "chatroom"}' \
- --data-urlencode 'skip=1' \
- --data-urlencode 'limit=20' \
- https://{{host}}/1.2/rtm/chatrooms
-```
-
-| Parameter | Constraint | Description |
-| ----- | ---- | ------------------------------------------------------------------------ |
-| skip | Optional |
-| limit | Optional | Can be used for pagination when used together with `skip` |
-| where | Optional | See [Data Storage REST API](/sdk/storage/guide/rest/) for more information |
-
-The response looks like
-
-```json
-{
- "results": [
- {
- "name": "My First Chatroom",
- "createdAt": "2018-01-17T04:15:33.386Z",
- "updatedAt": "2018-01-17T04:15:33.386Z",
- "objectId": "5a5ecde6c3422b738c8779d7"
- }
- ]
-}
-```
-
-### Updating Chat Rooms
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"name":"Updated Chatroom"}' \
- https://{{host}}/1.2/rtm/chatrooms/{chatroom_id}
-```
-
-The response looks like
-
-```json
-{
- "updatedAt": "2018-01-16T03:40:37.683Z",
- "objectId": "5a5d7433c3422b31ed845e76"
-}
-```
-
-### Deleting Chat Rooms
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/chatrooms/{chatroom_id}
-```
-
-The response looks like
-
-```json
-{}
-```
-
-### Randomly Retrieving Some Online Members
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/chatrooms/{chatroom_id}/members
-```
-
-The response looks like
-
-```json
-{ "result": ["clientid1", "clientid2", "clientid3"] }
-```
-
-### Retrieving Numbers of Online Members
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/chatrooms/{chatroom_id}/members/online-count
-```
-
-The response looks like
-
-```json
-{ "result": 3 }
-```
-
-### Chat Rooms: Sending Messages
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "", "message": ""}' \
- https://{{host}}/1.2/rtm/chatrooms/{chatroom_id}/messages
-```
-
-**Attention**: Due to the administrative essence of this interface, when you send messages with this interface, our system won’t check if the **from_client** has the permission to send messages to the conversation.
-If you are using rich media messages in your application, please make sure to have the **message** field follow the required format.
-Besides, for chat rooms, messages **cannot** be synced to the online **from_client**.
-
-| Parameter | Constraint | Description |
-| ------------------ | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| from_client | Required | The client ID of the sender. |
-| message | Required | The content of the message. Although the value of this field is a string, there is no limit on the format of the string. Therefore, you can define messages with different formats using this field, as long as the size of the value of this field doesn’t exceed the limit of 5 KB. |
-| transient | Optional | Whether the message is transient. Defaults to `false`. |
-| priority | Optional | The priority of the message. Could be `high`, `normal`, or `low`. The value is case-insensitive. Defaults to `normal`. This parameter is only valid if the message is transient or is sent to a chat room. When there is high traffic on the server or users’ devices, messages with high priorities will still be queued. |
-| mention_all | Optional | Boolean. Used to remind all the members in the conversation to pay attention to this message. |
-| mention_client_ids | Optional | Array. Includes the list of `client_id` that will be reminded to pay attention to this message. Can contain at most 20 client IDs. |
-
-Response:
-
-By default, messages are sent asynchronously with the API. You will receive the ID of the message along with the server timestamp, like `{"msg-id":"qNkRkFWOeSqP65S9fDyHJw", "timestamp":1495431811151}`.
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### Retrieving History Messages
-
-This interface can only be accessed with the master key.
-To ensure the security of history messages, you can enable the signing mechanism. See [3. Security, Permission Management, Chat Rooms, and Temporary Conversations](/sdk/im/guide/senior/) for more information.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- https://{{host}}/1.2/rtm/chatrooms/{conv_id}/messages
-```
-
-| Parameter | Constraint | Description |
-| -------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------ |
-| msgid | Optional | The ID of the starting message. **When this parameter is provided, the `timestamp` of the corresponding message must be provided as well.** |
-| timestamp | Optional | The start timestamp (in milliseconds). Defaults to the current time. |
-| till_msgid | Optional | The ID of the ending message. **When this parameter is provided, the `till_timestamp` of the corresponding message must be provided as well.** |
-| till_timestamp | Optional | The end timestamp (in milliseconds). Defaults to 0. |
-| include_start | Optional | Whether to include the message determined by `timestamp` and `msgid`. Boolean. Defaults to `false`. |
-| include_stop | Optional | Whether to include the message determined by `till_timestamp` and `till_msgid`. Boolean. Defaults to `false`. |
-| reversed | Optional | Sort the results in reverse of the default order (time order). With this option enabled, `till_timestamp` defaults to the current time and `timestamp` defaults to 0. Boolean. Defaults to `false`. |
-| limit | Optional | Used to limit the number of records returned. Defaults to 100. Can be no more than 1000. |
-| client_id | Optional | Viewer ID (used for signature). |
-| nonce | Optional | Nonce for the signature (used for signature). |
-| signature_ts | Optional | Timestamp for the signature (used for signature) in milliseconds. |
-| signature | Optional | The signature (used for signature). |
-
-This interface contains a lot of parameters related to time. To make things clear, here is an example. Assuming that a conversation has 3 messages with their IDs being `id1`, `id2`, and `id3` and timestamps being `t1`, `t2`, and `t3` (`t1` < `t2` < `t3`). The table below shows the results for queries with different parameters (blank cells indicate that the default values are used):
-
-| timestamp | msgid | till_timestamp | till_msgid | include_start | include_stop | reversed | Result |
-| --------- | ----- | -------------- | ---------- | ------------- | ------------ | -------- | ------- |
-| t3 | id3 | t1 | id1 | | | | id2 |
-| t3 | id3 | t1 | id1 | true | | | id3 id2 |
-| t3 | id3 | t1 | id1 | | true | | id2 id1 |
-| t1 | id1 | t3 | id3 | | | true | id2 |
-| t1 | id1 | t3 | id3 | true | | true | id1 id2 |
-| t1 | id1 | t3 | id3 | | true | true | id2 id3 |
-
-The response would be a JSON array containing messages sorted from the newest to the oldest. If `reversed` is enabled, the messages will be sorted in reverse order.
-
-Response:
-
-```json
-[
- {
- "timestamp": 1408008498571,
- "conv-id": "219946ef32e40c515d33ae6975a5c593",
- "data": "Nice weather!",
- "from": "u111872755_9d0461adf9c267ae263b3742c60fa",
- "msg-id": "vdkGm4dtRNmhQ5gqUTFBiA",
- "is-conv": true,
- "is-room": false,
- "to": "5541c02ce4b0f83f4d44414e",
- "bin": false,
- "from-ip": "202.117.15.217"
- }
- // ...
-]
-```
-
-To look up all the messages sent by a user, use `GET /rtm/clients/{client_id}/messages`.
-To look up all the messages sent through your application, use `GET /rtm/messages`.
-
-### Chat Rooms: Updating Messages
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "", "message": "", "timestamp": 123}' \
- https://{{host}}/1.2/rtm/chatrooms/{chatroom_id}/messages/{message_id}
-```
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ---------------------- |
-| from_client | Required | The client ID of the sender |
-| message | Required | The message |
-| timestamp | Required | The timestamp of the message |
-
-If successful, the `200 OK` status code will be returned.
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### Chat Rooms: Recalling Messages
-
-This interface can only be accessed with the master key. SDK support is needed for requests sent to this interface to take effect. Refer to the interface for updating messages for more information.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "", "timestamp": 123}' \
- https://{{host}}/1.2/rtm/chatrooms/{chatroom_id}/messages/{message_id}/recall
-```
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ---------------------- |
-| from_client | Required | The client ID of the sender |
-| timestamp | Required | The timestamp of the message |
-
-If successful, the `200 OK` status code will be returned.
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### Deleting Messages
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -G \
- --data-urlencode 'from_client=some-client-id' \
- --data-urlencode 'timestamp=123' \
- https://{{host}}/1.2/rtm/chatrooms/{chatroom_id}/messages/{message_id}
-```
-
-Keep in mind that this interface will only delete messages on the server and won’t affect clients.
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ---------------------- |
-| from_client | Required | The client ID of the sender |
-| timestamp | Required | The timestamp of the message |
-
-Response:
-
-```json
-{}
-```
-
-## System Conversations
-
-### Creating System Conversations
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"name":"My First Service-conversation"}' \
- https://{{host}}/1.2/rtm/service-conversations
-```
-
-See [Instant Messaging Overview](/sdk/im/guide/overview/) for more information about the attributes of a conversation.
-
-The response looks like
-
-```json
-{
- "objectId": "5a5d7432c3422b31ed845e75",
- "createdAt": "2018-01-16T03:40:32.814Z"
-}
-```
-
-### Retrieving System Conversations
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -G \
- --data-urlencode 'where={"name": "service"}' \
- --data-urlencode 'skip=1' \
- --data-urlencode 'limit=20' \
- https://{{host}}/1.2/rtm/service-conversations
-```
-
-| Parameter | Constraint | Description |
-| ----- | ---- | ------------------------------------------------------------------------ |
-| skip | Optional |
-| limit | Optional | Can be used for pagination when used together with `skip` |
-| where | Optional | See [Data Storage REST API](/sdk/storage/guide/rest/) for more information |
-
-The response looks like
-
-```json
-{
- "results": [
- {
- "name": "My First Service-conversation",
- "createdAt": "2018-01-17T04:15:33.386Z",
- "updatedAt": "2018-01-17T04:15:33.386Z",
- "objectId": "5a5ecde6c3422b738c8779d7"
- }
- ]
-}
-```
-
-### Updating System Conversations
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"name":"Updated Service-conversation"}' \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}
-```
-
-The response looks like
-
-```json
-{
- "updatedAt": "2018-01-16T03:40:37.683Z",
- "objectId": "5a5d7433c3422b31ed845e76"
-}
-```
-
-### Deleting System Conversations
-
-With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}
-```
-
-The response looks like
-
-```json
-{}
-```
-
-### Subscribing System Conversations
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"client_id":"client_id"}' \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}/subscribers
-```
-
-The response looks like
-
-```json
-{}
-```
-
-### Unsubscribing System Conversations
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}/subscribers/{client_id}
-```
-
-The response looks like
-
-```json
-{}
-```
-
-### Retrieving Subscribers
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}/subscribers
-```
-
-| Parameter | Constraint | Description |
-| --------- | ---- | ------------------------------------------------------------------------------------------------------------ |
-| limit | Optional | Used to limit the number of records returned. Defaults to 50. Can be no more than 50. |
-| client_id | Optional | The client ID to start retrieving from. If left blank, the system will start from the first subscriber in the list. The result will not contain the client ID specified here. |
-
-The response looks like
-
-```json
-[
- {
- "timestamp": 1491467841116,
- "subscriber": "client id 1",
- "conv_id": "55b871"
- },
- {
- "timestamp": 1491467852768,
- "subscriber": "client id 2",
- "conv_id": "55b872"
- }
- // ...
-]
-```
-
-Here `timestamp` is the time the user subscribed to the system conversation. `subscriber` is the client ID of the subscriber. If the result doesn’t contain all the subscribers and you need to retrieve more records, you can send another request with the last client ID from the result as the `client_id` of the request.
-
-### Retrieving Numbers of Subscribers
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}/subscribers/count
-```
-
-The response looks like
-
-```json
-{ "count": 100 }
-```
-
-### Messaging All Subscribers
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "", "message": ""}' \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}/broadcasts
-```
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ---------------------------------------------------------------------------------------- |
-| from_client | Required | The client ID of the sender. |
-| message | Required | The message content. |
-| push | Optional | The content for push notifications. If specified, all iOS and Android users will receive a push notification with this content. String or JSON object. |
-
-Response:
-
-```json
-{ "msg-id": "qNkRkFWOeSqP65S9fDyHJw", "timestamp": 1495431811151 }
-```
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### Updating Messages Sent to All Subscribers
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "", "message": "", "timestamp": 123}' \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}/messages/{message_id}
-```
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ---------------------- |
-| from_client | Required | The client ID of the sender. |
-| message | Required | The message content. |
-| timestamp | Required | The timestamp of the message. |
-
-If successful, the `200 OK` status code will be returned.
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### Recalling Messages Sent to All Subscribers
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "", "timestamp": 123}' \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}/messages/{message_id}/recall
-```
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ---------------------- |
-| from_client | Required | The client ID of the sender. |
-| timestamp | Required | The timestamp of the message. |
-
-If successful, the `200 OK` status code will be returned.
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### Messaging Specific Users
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "", "message": ""}' \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}/messages
-```
-
-**Attention**: Due to the administrative essence of this interface, when you send messages with this interface, our system won’t check if the **from_client** has the permission to send messages to the conversation.
-If you are using rich media messages in your application, please make sure to have the **message** field follow the required format.
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| from_client | Required | The client ID of the sender. |
-| to_clients | Required | An array of client IDs that will receive the message. Can contain at most 20 client IDs. |
-| message | Required | The content of the message. Although the value of this field is a string, there is no limit on the format of the string. Therefore, you can define messages with different formats using this field, as long as the size of the value of this field doesn’t exceed the limit of 5 KB. |
-| transient | Optional | Whether the message is transient. Defaults to `false`. |
-| no_sync | Optional | By default, the message will be synced to the client of the `from_client` that is online. You can disable this feature by setting this property to `true`. |
-| push_data | Optional | The content used for push notifications. If the receiver uses an iOS device and is not online, the value of this property will be used for sending push notifications. See [2. Advanced Messaging Features, Push Notifications, Synchronization, and Multi-Device Sign-on](/sdk/im/guide/intermediate/) for more information. |
-| priority | Optional | The priority of the message. Could be `high`, `normal`, or `low`. The value is case-insensitive. Defaults to `normal`. This parameter is only valid if the message is transient or is sent to a chat room. When there is high traffic on the server or users’ devices, messages with high priorities will still be queued. |
-
-Response:
-
-By default, messages are sent asynchronously with the API. You will receive the ID of the message along with the server timestamp, like `{"msg-id":"qNkRkFWOeSqP65S9fDyHJw", "timestamp":1495431811151}`.
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### Updating Messages Sent to Specific Users
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "", "message": "", "timestamp": 123, "to_clients":["a","b","c"]}' \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}/messages/{message_id}
-```
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ----------------------------------------------------------------------- |
-| from_client | Required | The client ID of the sender. |
-| message | Required | The message content. |
-| timestamp | Required | The timestamp of the message. |
-| to_clients | Required | An array of client IDs that will receive the message. Can contain at most 20 client IDs. |
-
-If successful, the `200 OK` status code will be returned.
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### Recalling Messages Sent to Specific Users
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "", "timestamp": 123, "to_clients":["a","b","c"]}' \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}/messages/{message_id}/recall
-```
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ----------------------------------------------------------------------- |
-| from_client | Required | The client ID of the sender. |
-| timestamp | Required | The timestamp of the message. |
-| to_clients | Required | An array of client IDs that will receive the message. Can contain at most 20 client IDs. |
-
-If successful, the `200 OK` status code will be returned.
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### Deleting Messages Sent to Specific Users
-
-Invoking this interface requires the master key. You can only delete messages sent through system conversations or sent to specific users. You cannot delete broadcast messages with this interface.
-To delete broadcast messages, use `DELETE /1.2/rtm/broadcasts/{message_id}` instead.
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -G \
- --data-urlencode 'from_client=some-client-id' \
- --data-urlencode 'timestamp=123' \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}/subscribers/{client_id}/messages/{message_id}
-```
-
-Keep in mind that this interface will only delete messages on the server and won’t affect clients.
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ---------------------- |
-| from_client | Required | The client ID of the sender. |
-| timestamp | Required | The timestamp of the message. |
-
-Response:
-
-```json
-{}
-```
-
-### Retrieving Messages Sent to Specific Users
-
-This interface can only be accessed with the master key. The result contains messages sent to all users as well as those sent to specific users.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}/subscribers/{client_id}/messages
-```
-
-The parameters and response formats of this interface are the same as those for retrieving history messages.
-
-## Users
-
-### Retrieving Online Members
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"client_ids": ["Tom", "Jerry"]}' \
- https://{{host}}/1.2/rtm/clients/check-online
-```
-
-| Parameter | Constraint | Description |
-| ---------- | ---- | ----------------------------------- |
-| client_ids | Required | A list of client IDs (no more than 20) to look up. |
-
-The response contains the IDs of the online members
-
-```json
-{ "results": ["client1"] }
-```
-
-Keep in mind that this interface doesn’t check if a user exists. If a user doesn’t exist, the user will be considered offline.
-
-### Retrieving Numbers of Unread Messages
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'conv_id=...' \
- https://{{host}}/1.2/rtm/clients/{client_id}/unread-count
-```
-
-| Parameter | Constraint | Description |
-| ------- | ---- | ----------------------------------------------------------- |
-| conv_id | Optional | Conversation ID. If omitted, the numbers of unread messages of all conversations will be returned. |
-
-The response looks like
-
-```json
-{ "count": 1 }
-```
-
-### Forcing Users to Log Out
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"reason": "why"}' \
- https://{{host}}/1.2/rtm/clients/{client_id}/kick
-```
-
-| Parameter | Constraint | Description |
-| ------ | ---- | ---------------------------------- |
-| reason | Optional | A string indicating the reason. Can contain more than 20 characters. |
-
-The response looks like
-
-```json
-{}
-```
-
-### Retrieving Subscribed System Conversations
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -G \
- --data-urlencode 'conv_id=...' \
- --data-urlencode 'timestamp=...' \
- --data-urlencode 'limit=...' \
- --data-urlencode 'direction=...' \
- https://{{host}}/1.2/rtm/clients/{client_id}/service-conversations
-```
-
-| Parameter | Constraint | Type | Description |
-| --------- | ---- | ------ | -------------------------------------------------------------------------------------------------------------------------------- |
-| conv_id | Optional | String | The conversation ID to start retrieving from. If left blank, the system will start from the first conversation in the list. The result will not contain the conversation specified here. |
-| timestamp | Optional | Number | The subscription time to start from (in milliseconds). Although this parameter is optional, it becomes required when `conv_id` is provided and the value shall be the time that matches the `conv_id` specified. |
-| limit | Optional | Number | Limit the number of records returned. Defaults to 50. |
-| direction | Optional | String | The order to sort the result. `old` means descending order and `new` means ascending order. Defaults to `new`. If using `old`, the latest subscribed conversation will be returned first; if using `new`, the earliest subscribed conversation will be returned first. |
-
-The response contains the system conversations subscribed by the target user:
-
-```json
-[
- { "timestamp": 1482994126561, "subscriber": "XXX", "conv_id": "convId1" },
- { "timestamp": 1491467945277, "subscriber": "XXX", "conv_id": "convId2" }
- // ...
-]
-```
-
-Here `timestamp` is the time the user subscribed to the system conversation. `subscriber` is the client ID of the subscriber. If the result doesn’t contain all the conversations and you need to retrieve more records, you can send another request with the last conversation ID from the result as the `conv_id` of the request and its timestamp as the `timestamp` of the request.
-
-### Retrieving Messages Sent by Users
-
-This interface can only be accessed with the master key.
-Use this interface to retrieve all the messages sent from a `client_id` to one-on-one chats, group chats, and chat rooms.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/clients/{client_id}/messages
-```
-
-The parameters and response formats of this interface are the same as those for `GET /1.2/rtm/conversations/{conv_id}/messages`.
-
-### Obtaining Signatures for Logging In
-
-If your app uses `LCUser`, you can have your app quickly authenticate users with this interface.
-This feature is disabled by default. You can enable it by going to **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Instant Messaging settings** and enabling **Verify signatures for logging in**.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'session_token=some-token' \
- https://{{host}}/1.2/rtm/clients/sign
-```
-
-| Parameter | Constraint | Description |
-| ------------- | ---- | ---------------------- |
-| session_token | Required | The `sessionToken` of the `LCUser` |
-
-The response looks like
-
-```json
-{
- "signature": "bc884dbb617aab1efc228229210e487330abfc7d",
- "nonce": "akywke3f28",
- "client_id": "5fb4ff18d0deed36ea501c8a",
- "timestamp": 1614237989966
-}
-```
-
-Keep in mind that although this interface takes `GET` requests, requests are not idempotent. Each invocation will get you a different signature.
-
-This interface comes with the `_rtmClientSign` hook, which gets invoked once `sessionToken` is validated. The argument passed into the hook is a JSON object that represents the `LCUser`:
-
-```json
-{
- "email": "",
- "sessionToken": "",
- "updatedAt": "", // Format: 2017-07-11T07:58:10.149Z
- "phone": "",
- "objectId": "",
- "username": "",
- "createdAt": "", // Format: 2017-07-11T07:58:10.149Z
- "emailVerified": true, // true/false
- "mobilePhoneVerified": true // true/false
-}
-```
-
-There are two possible results:
-
-```json
-{"result": true} // Allow to sign
-{"result": false, "error": "error message"} // Reject to sign
-```
-
-## Global APIs
-
-### Retrieving User Count
-
-This interface will return the number of online users as well as the number of users who logged in today. Invoking this interface requires the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/stats
-```
-
-The response looks like
-
-```json
-{ "result": { "online_user_count": 10212, "user_count_today": 1002324 } }
-```
-
-Here `online_user_count` is the number of online users and `user_count_today` is the number of users who logged in today.
-
-### Retrieving All Conversations
-
-This interface will return all the conversations, including one-on-one chats, group chats, chat rooms, and system conversations. With the default ACL settings of the `_Conversation` table, this interface can only be accessed with the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/all-conversations
-```
-
-| Parameter | Constraint | Description |
-| ----- | ---- | ------------------------------------------------------------------------ |
-| skip | Optional |
-| limit | Optional | Can be used for pagination when used together with `skip` |
-| where | Optional | See [Data Storage REST API](/sdk/storage/guide/rest/) for more information |
-
-The response looks like
-
-```json
-{
- "results": [
- {
- "name": "conversation",
- "createdAt": "2018-01-17T04:15:33.386Z",
- "updatedAt": "2018-01-17T04:15:33.386Z",
- "objectId": "5a5ecde6c3422b738c8779d7"
- }
- ]
-}
-```
-
-### Sending Broadcast Messages
-
-This interface can be used to send broadcast messages to all the clients in your application. You can send at most 30 broadcast messages everyday. Invoking this interface requires the master key.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "1a", "message": "{\"_lctype\":-1,\"_lctext\":\"This is a text message\",\"_lcattrs\":{\"a\":\"_lcattrs can be used to store custom key-value pairs\"}}", "conv_id": "..."}' \
- https://{{host}}/1.2/rtm/broadcasts
-```
-
-| Parameter | Constraint | Type | Description |
-| ----------- | ---- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
-| from_client | Required | String | The client ID of the sender. |
-| conv_id | Required | String | The ID of the conversation. For system conversations only. |
-| message | Required | String | The content of the message. Although the value of this field is a string, there is no limit on the format of the string. Therefore, you can define messages with different formats using this field, as long as the size of the value of this field doesn’t exceed the limit of 5 KB. |
-| valid_till | Optional | Number | Expiration time in UTC timestamp (milliseconds). Can be no later than 1 month. Defaults to 1 month. |
-| push | Optional | String or JSON object | The content for push notifications. If specified, **all** iOS and Android users will receive a push notification with this content. |
-| transient | Optional | Boolean | Whether the message is transient. Defaults to `false`. Transient messages can only be received by online users. Offline users won’t see those messages when they get online. |
-
-Push 的格式与《推送 REST API 指南》的《消息内容参数》一节中 `data` 下面的部分一致。如果你需要指定开发证书推送,需要在 push 的 json 中设置 `"_profile": "dev"`,例如:
-
-```json
-{
- "alert": "消息内容",
- "category": "通知分类名称",
- "badge": "Increment",
- "_profile": "dev"
-}
-```
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### Updating Broadcast Messages
-
-This interface can only be accessed with the master key.
-
-Updating broadcast messages only works for devices that haven’t received the messages yet. Devices that have already received the messages won’t see the updated messages, so please be careful when sending broadcast messages.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_client": "", "message": "", "timestamp": 123}' \
- https://{{host}}/1.2/rtm/service-conversations/{conv_id}/messages/{message_id}
-```
-
-| Parameter | Constraint | Description |
-| ----------- | ---- | ---------------------- |
-| from_client | Required | The client ID of the sender. |
-| message | Required | The message content. |
-| timestamp | Required | The timestamp of the message. |
-
-If successful, the `200 OK` status code will be returned.
-
-Rate limits:
-
-This interface has rate limits enforced. See [Rate Limits](#rate-limits) for more information.
-
-### Deleting Broadcast Messages
-
-This API can be used to delete published broadcast messages. Deleting broadcast messages only works for devices that haven’t received the messages yet. Devices that have already received the messages won’t see the messages deleted. Invoking this interface requires the master key.
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/broadcasts/{message_id}
-```
-
-| Parameter | Constraint | Description |
-| ---------- | ---- | ----------------------- |
-| message_id | Required | The ID of the message to be deleted. String. |
-
-If successful, the `200 OK` status code will be returned.
-
-### Retrieving Broadcast Messages
-
-This API can be used to retrieve the valid broadcast messages. Invoking this interface requires the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/broadcasts?conv_id={conv_id}
-```
-
-| Parameter | Constraint | Description |
-| ------- | ---- | ---------------------- |
-| conv_id | Required | The ID of the system conversation. |
-| limit | Optional | The number of messages returned. |
-| skip | Optional | The number of messages skipped. Used for pagination. |
-
-### Retrieving All History Messages in an Application
-
-This interface can only be accessed with the master key.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.2/rtm/messages
-```
-
-The parameters and response formats of this interface are the same as those for `GET /1.2/rtm/conversations/{conv_id}/messages`.
-
-## Rich Media Messages
-
-The only difference between the parameter format of [rich media messages](/sdk/im/guide/overview/#default-types-for-rich-media-messages) and that for basic messages is that the value of the `message` parameter for a rich media message is a string containing a JSON.
-See [Instant Messaging Overview · Default Types for Rich Media Messages](/sdk/im/guide/overview/#default-types-for-rich-media-messages) for the meanings of the fields in the JSON.
-Below are some examples of rich media messages derived from predefined types that are serialized to JSON objects.
-
-### Text
-
-```json
-{
- "_lctype": -1,
- "_lctext": "This is a text message",
- "_lcattrs": {
- "a": "_lcattrs can be used to store custom key-value pairs"
- }
-}
-```
-
-### Image
-
-```json
-{
- "_lctype": -2, // Required parameter
- "_lctext": "Image description",
- "_lcattrs": {
- "a": "_lcattrs can be used to store custom key-value pairs",
- "b": true,
- "c": 12
- },
- "_lcfile": {
- "url": "http://ac-p2bpmgci.clouddn.com/246b8acc-2e12-4a9d-a255-8d17a3059d25", // Required parameter
- "objId": "54699d87e4b0a56c64f470a4", // The LCFile.objectId of the file
- "metaData": {
- "name": "IMG_20141223.jpeg", // Image name
- "format": "png", // Image format
- "height": 768, // Pixels
- "width": 1024, // Pixels
- "size": 18 // b
- }
- }
-}
-```
-
-Above is a full example. To only send the URL of an image:
-
-```json
-{
- "_lctype": -2,
- "_lcfile": {
- "url": "http://ac-p2bpmgci.clouddn.com/246b8acc-2e12-4a9d-a255-8d17a3059d25"
- }
-}
-```
-
-### Audio
-
-```json
-{
- "_lctype": -3,
- "_lctext": "This is an audio message",
- "_lcattrs": {
- "a": "_lcattrs can be used to store custom key-value pairs"
- },
- "_lcfile": {
- "url": "http://ac-p2bpmgci.clouddn.com/246b8acc-2e12-4a9d-a255-8d17a3059d25",
- "objId": "54699d87e4b0a56c64f470a4", // The LCFile.objectId of the file
- "metaData": {
- "name": "Never Gonna Give You Up.wav",
- "format": "wav",
- "duration": 26, // Seconds
- "size": 2738 // b
- }
- }
-}
-```
-
-Simplified version:
-
-```json
-{
- "_lctype": -3,
- "_lcfile": {
- "url": "http://www.somemusic.com/x.mp3"
- }
-}
-```
-
-### Video
-
-```json
-{
- "_lctype": -4,
- "_lctext": "This is a video message",
- "_lcattrs": {
- "a": "_lcattrs can be used to store custom key-value pairs"
- },
- "_lcfile": {
- "url": "http://ac-p2bpmgci.clouddn.com/99de0f45-171c-4fdd-82b8-1877b29bdd12",
- "objId": "54699d87e4b0a56c64f470a4", // The LCFile.objectId of the file
- "metaData": {
- "name": "video.mov",
- "format": "avi",
- "duration": 168, // Seconds
- "size": 18689 // b
- }
- }
-}
-```
-
-Simplified version:
-
-```json
-{
- "_lctype": -4,
- "_lcfile": {
- "url": "http://www.somevideo.com/Y.flv"
- }
-}
-```
-
-### File
-
-```json
-{
- "_lctype": -6,
- "_lctext": "This is a file",
- "_lcattrs": {
- "a": "_lcattrs can be used to store custom key-value pairs"
- },
- "_lcfile": {
- "url": "http://www.somefile.com/resume.doc",
- "name": "resume.doc",
- "size": 18689 // b
- }
-}
-```
-
-Simplified version:
-
-```json
-{
- "_lctype": -6,
- "_lcfile": {
- "url": "http://www.somefile.com/resume.doc",
- "name": "resume.doc"
- }
-}
-```
-
-### Location
-
-```json
-{
- "_lctype": -5,
- "_lctext": "This is a location message",
- "_lcattrs": {
- "a": "_lcattrs can be used to store custom key-value pairs"
- },
- "_lcloc": {
- "longitude": 23.2,
- "latitude": 45.2
- }
-}
-```
-
-Simplified version:
-
-```json
-{
- "_lctype": -5,
- "_lcloc": {
- "longitude": 23.2,
- "latitude": 45.2
- }
-}
-```
-
-## Rate Limits
-
-Rate limits are enforced on all the REST APIs on this page that perform operations on messages (**client SDKs are not affected by these limits**). Those limits are listed below:
-
-### Basic Messages
-
-Version 1.1:
-
-- Sending messages and having system conversations send messages to users (`/1.1/rtm/messages`)
-- Updating and recalling messages (`/1.1/rtm/patch/message`)
-
-Version 1.2:
-
-- [One-On-One Chats and Group Chats: Sending Messages](#one-on-one-chats-and-group-chats-sending-messages)
-- [One-On-One Chats and Group Chats: Updating Messages](#one-on-one-chats-and-group-chats-updating-messages)
-- [One-On-One Chats and Group Chats: Recalling Messages](#one-on-one-chats-and-group-chats-recalling-messages)
-- [Chat Rooms: Sending Messages](#chat-rooms-sending-messages)
-- [Chat Rooms: Updating Messages](#chat-rooms-updating-messages)
-- [Chat Rooms: Recalling Messages](#chat-rooms-recalling-messages)
-- [System Conversations: Messaging Specific Users](#messaging-specific-users)
-- [System Conversations: Updating Messages Sent to Specific Users](#updating-messages-sent-to-specific-users)
-- [System Conversations: Recalling Messages Sent to Specific Users](#recalling-messages-sent-to-specific-users)
-
-#### Limits
-
-| Business Plan (per application) | Developer Plan (per application) |
-| -------------------------------------- | ---------------- |
-| No more than 9000 requests per minute; defaults to 1800 requests per minute | 120 requests per minute |
-
-The limit is shared by all interfaces. If the rate limit is exceeded, the server will reject further requests with the 429 error code until 1 minute later.
-
-If your application has a Business Plan, you can edit its limit by going to **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Thresholds > Frequency limit for calling API for basic messages**.
-You will be billed each day according to the peak request rate that occurred on the day:
-
-| Requests per minute | Price |
-| -------------- | ----------- |
-| 0 to 1800 | Free |
-| 1801 to 3600 | ¥20 CNY per day |
-| 3601 to 5400 | ¥30 CNY per day |
-| 5401 to 7200 | ¥40 CNY per day |
-| 7201 to 9000 | ¥50 CNY per day |
-
-International version
-
-| Requests per minute | Price |
-| -------------- | ------------ |
-| 0 to 1800 | Free |
-| 1801 to 3600 | $6 USD per day |
-| 3601 to 5400 | $9 USD per day |
-| 5401 to 7200 | $12 USD per day |
-| 7201 to 9000 | $15 USD per day |
-
-The peak request rate that occurred on each day can be viewed on **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Statistics > REST API Peak QPM**.
-
-### Messages Sent From System Conversations
-
-Version 1.1:
-
-- Sending messages from system conversations (`/1.1/rtm/broadcast/subscriber`)
-
-Version 1.2:
-
-- [Messaging All Subscribers](#messaging-all-subscribers)
-- [Updating Messages Sent to All Subscribers](#updating-messages-sent-to-all-subscribers)
-- [Recalling Messages Sent to All Subscribers](#recalling-messages-sent-to-all-subscribers)
-
-#### Limits
-
-| Limit | Business Plan | Developer Plan |
-| -------- | ------------------ | ------------------ |
-| Rate Limit | 30 requests per minute per application | 10 requests per minute per application |
-| Quota | 1000 requests per day | 100 requests per day |
-
-The limit is shared by all interfaces. If the rate limit is exceeded, the server will reject further requests with the 429 error code until 1 minute later. If the quota is exceeded, the server will reject further requests with the 429 error code until the next day.
-
-### Broadcast Messages
-
-Version 1.1:
-
-- Sending broadcast messages (`/1.1/rtm/broadcast`)
-
-Version 1.2:
-
-- [Sending Broadcast Messages](#sending-broadcast-messages)
-- [Updating Broadcast Messages](#updating-broadcast-messages)
-
-#### Limits
-
-| Limit | Business Plan | Developer Plan |
-| -------- | ------------------ | ----------------- |
-| Rate Limit | 10 requests per minute per application | 1 request per minute per application |
-| Quota | 30 requests per day | 10 requests per day |
-
-The limit is shared by all interfaces. If the rate limit is exceeded, the server will reject further requests with the 429 error code until 1 minute later. If the quota is exceeded, the server will reject further requests with the 429 error code until the next day.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/senior.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/senior.mdx
deleted file mode 100644
index 37bc09957..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/senior.mdx
+++ /dev/null
@@ -1,1131 +0,0 @@
----
-title: 3. Security, Chat Rooms, and Temporary Conversations
-sidebar_label: Permission and Chat Rooms
-sidebar_position: 3
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import Mermaid from "/src/docComponents/Mermaid";
-import { Conditional } from "/src/docComponents/conditional";
-
-## Introduction
-
-In the previous chapter, [Advanced Messaging Features, Push Notifications, Synchronization, and Multi-Device Sign-on](/sdk/im/guide/intermediate/), we introduced a number of bonus features that you can implement beyond basic messaging. In this chapter, we will introduce more features from the perspectives of system security and permission management, including:
-
-- How to verify the requests made by clients with a third-party signing mechanism
-- How to control the permissions each user has
-- How to build a chat room with an unlimited number of people
-- How to enforce text moderation on the messages sent by users
-- How to implement temporary conversations
-
-## Signing Mechanism
-
-Instant Messaging is decoupled from the account system offered by Data Storage. This makes it possible for you to use Instant Messaging even though the account system of your app is not built with Data Storage. To ensure the security of your app, we offer a third-party signing mechanism that helps your app verify all the requests sent from clients.
-
-The mechanism comes with an authentication server (the so-called "third party") deployed between clients and the cloud. Each time a client wants to make a request involving sensitive operations (like logging in, creating conversations, joining conversations, or inviting users), it has to obtain a signature from the authentication server. The signature gets attached to the request and will be verified by the cloud according to a predefined protocol. Only those requests with valid signatures will be accepted by the cloud.
-
-The signing mechanism is turned off by default. You can turn it on by going to **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Instant Messaging settings**:
-
-- **Verify signatures for logging in**: Verify all the activities of logging in
-- **Verify signatures for conversation operations**: Verify all the activities of creating conversations, joining conversations, inviting users, and removing users
-- **Verify signatures for retrieving history messages**: Verify all the activities of retrieving history messages
-
-You are free to change the settings here based on your app's actual needs, though we highly recommend that you at least keep **verifying signatures for logging in** on, which guarantees the basic security of your app.
-
->Authentication Server: 1. Apply for signature with request
-Authentication Server-->>Client: 2. Return timestamp, nonce, and signature to the client
-Client->>Authentication Server: 3. Send the request to the cloud with the signature
-Authentication Server-->>Client: 4. Verify the signature along with the request
-`}
-/>
-
-1. When the client performs operations like logging in or creating conversations, the SDK applies for a signature by calling `SignatureFactory` with the user's information and a request containing the operations to be done.
-2. The authentication server checks if the operations are performed with enough permissions. If that's true, the server will follow the [signing algorithm](#signatures-for-logging-in) that will be mentioned later to generate the timestamp, nonce, and signature, and send them back to the client.
-3. The client attaches the signature to the request and sends them to the cloud.
-4. The cloud verifies the signature along with the request to ensure that the operations in the request are allowed. The request will be accepted only if the signature is valid.
-
-The algorithm used for the signing process is **HMAC-SHA1** and the output would be the hex dump of a byte stream. For different requests, different strings with different UTC timestamps and nonces need to be constructed.
-
-If you are using `LCUser` in your app, you can get signatures for logging in through our REST API.
-
-### Formats of Signatures
-
-Below we will introduce the formats of strings used to obtain signatures for different types of operations.
-
-#### Signatures for Logging in
-
-Below is the format of strings for logging in. Notice that there are _two colons_ between `clientid` and `timestamp`:
-
-```
-appid:clientid::timestamp:nonce
-```
-
-| Parameter | Description |
-| ----------- | ------------------------------------------------------------------------ |
-| `appid` | Your App ID. |
-| `clientid` | The `clientId` used for logging in. |
-| `timestamp` | The number of **milliseconds** that have elapsed since Unix epoch (UTC). |
-| `nonce` | A random string. |
-
-> Note: The key for signing has to be the **Master Key** of your app. You can find it from **Developer Center > Your game > Game Services > Configuration**. **Make sure your Master Key is well-protected and doesn't get leaked.**
-
-You may implement your own `SignatureFactory` to retrieve signatures from remote servers. If you don't have your own server, you may use the **web hosting** service provided by Cloud Engine. Generating signatures within your mobile app is **extremely dangerous** since your **Master Key** can get exposed.
-
-This signature expires in 6 hours, but it becomes invalid immediately once the client has been forced to log out.
-The signature invalidness does not affect the currently connected clients.
-
-#### Signatures for Creating Conversations
-
-Below is the format of strings for creating conversations:
-
-```
-appid:clientid:sorted_member_ids:timestamp:nonce
-```
-
-- `appid`, `clientid`, `timestamp`, and `nonce` are [the same as above](#signatures-for-logging-in).
-- `sorted_member_ids` is a list of `clientId`s (users being invited to the conversation) arranged in **ascending order** and divided by colon (`:`).
-
-#### Signatures for Group Operations
-
-Below is the format of strings for **joining conversations**, **inviting users**, and **removing users**:
-
-```
-appid:clientid:convid:sorted_member_ids:timestamp:nonce:action
-```
-
-- `appid`, `clientid`, `sorted_member_ids`, `timestamp`, and `nonce` are the same as above. `sorted_member_ids` would be an empty string if you are creating a new conversation.
-- `convid` is the conversation ID.
-- `action` is the operation being performed: `invite` means joining a conversation or inviting users and `kick` means removing users.
-
-#### Signatures for Retrieving Message Histories
-
-```
-appid:client_id:convid:nonce:timestamp
-```
-
-The meanings of these parameters are the same as above.
-
-This signature is only used for REST API. It is not applicable to client-side SDKs.
-
-### Demo for Generating Signatures on Cloud Engine
-
-To help you better understand the signing algorithm, we made a server-side signing program based on Node.js and Cloud Engine. It's available [here](https://github.com/leancloud/leanengine-nodejs-demos/blob/master/functions/rtm-signature.js) for you to study and use.
-
-### Supporting Signatures on the Client Side
-
-So far we have been talking about the protocol used by the authentication server to generate signatures. Now let's see what we need to do with the client side to make the entire signing mechanism work.
-
-The SDK reserves a factory interface `Signature` for each `AVIMClient` instance. To enable signing, implement the interface with a class that calls the signing method on the authentication server to get signatures, and then bind the class to the `AVIMClient` instance:
-
-
-
-```cs
-public class LocalSignatureFactory : ILCIMSignatureFactory {
- const string MasterKey = "pyvbNSh5jXsuFQ3C8EgnIdhw";
-
- public Task CreateConnectSignature(string clientId) {
- long timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
- string nonce = NewNonce();
- string signature = GenerateSignature(LCApplication.AppId, clientId, string.Empty, timestamp.ToString(), nonce);
- return Task.FromResult(new LCIMSignature {
- Signature = signature,
- Timestamp = timestamp,
- Nonce = nonce
- });
- }
-
- public Task CreateStartConversationSignature(string clientId, IEnumerable memberIds) {
- string sortedMemberIds = string.Empty;
- if (memberIds != null) {
- List sortedMemberList = memberIds.ToList();
- sortedMemberList.Sort();
- sortedMemberIds = string.Join(":", sortedMemberList);
- }
- long timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
- string nonce = NewNonce();
- string signature = GenerateSignature(LCApplication.AppId, clientId, sortedMemberIds, timestamp.ToString(), nonce);
- return Task.FromResult(new LCIMSignature {
- Signature = signature,
- Timestamp = timestamp,
- Nonce = nonce
- });
- }
-
- public Task CreateConversationSignature(string conversationId, string clientId, IEnumerable memberIds, string action) {
- string sortedMemberIds = string.Empty;
- if (memberIds != null) {
- List sortedMemberList = memberIds.ToList();
- sortedMemberList.Sort();
- sortedMemberIds = string.Join(":", sortedMemberList);
- }
- long timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
- string nonce = NewNonce();
- string signature = GenerateSignature(LCApplication.AppId, clientId, conversationId, sortedMemberIds, timestamp.ToString(), nonce, action);
- return Task.FromResult(new LCIMSignature {
- Signature = signature,
- Timestamp = timestamp,
- Nonce = nonce
- });
- }
-
- private static string SignSHA1(string key, string text) {
- HMACSHA1 hmac = new HMACSHA1(Encoding.UTF8.GetBytes(key));
- byte[] bytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(text));
- string signature = BitConverter.ToString(bytes).Replace("-", string.Empty);
- return signature;
- }
-
- private static string NewNonce() {
- byte[] bytes = new byte[10];
- using (RandomNumberGenerator generator = RandomNumberGenerator.Create()) {
- generator.GetBytes(bytes);
- }
- return Convert.ToBase64String(bytes);
- }
-
- private static string GenerateSignature(params string[] args) {
- string text = string.Join(":", args);
- string signature = SignSHA1(MasterKey, text);
- return signature;
- }
-}
-
-// Specify the signature factory
-LCIMClient tom = new LCIMClient("tom", signatureFactory: new LocalSignatureFactory());
-```
-
-```java
-// An example of performing signing with Cloud Engine
-public class KeepAliveSignatureFactory implements SignatureFactory {
- @Override
- public Signature createSignature(String peerId, List watchIds) throws SignatureException {
- Map params = new HashMap();
- params.put("self_id",peerId);
- params.put("watch_ids",watchIds);
-
- try{
- Object result = LCCloud.callFunction("sign",params);
- if(result instanceof Map){
- Map serverSignature = (Map) result;
- Signature signature = new Signature();
- signature.setSignature((String)serverSignature.get("signature"));
- signature.setTimestamp((Long)serverSignature.get("timestamp"));
- signature.setNonce((String)serverSignature.get("nonce"));
- return signature;
- }
- }catch(LCException e){
- throw (SignatureFactory.SignatureException) e;
- }
- return null;
- }
-
- @Override
- public Signature createConversationSignature(String convId, String peerId,
- List targetPeerIds,String action) throws SignatureException{
- Map params = new HashMap();
- params.put("client_id",peerId);
- params.put("conv_id",convId);
- params.put("members",targetPeerIds);
- params.put("action",action);
-
- try{
- Object result = LCCloud.callFunction("sign2",params);
- if(result instanceof Map){
- Map serverSignature = (Map) result;
- Signature signature = new Signature();
- signature.setSignature((String)serverSignature.get("signature"));
- signature.setTimestamp((Long)serverSignature.get("timestamp"));
- signature.setNonce((String)serverSignature.get("nonce"));
- return signature;
- }
- }catch(LCException e){
- throw (SignatureFactory.SignatureException) e;
- }
- return null;
- }
-}
-
-// Bind an instance of the signature factory class to LCIMClient
-LCIMOptions.getGlobalOptions().setSignatureFactory(new KeepAliveSignatureFactory());
-```
-
-```objc
-// Implement the LCIMSignatureDataSource protocol
-- (void)client:(LCIMClient *)client
- action:(LCIMSignatureAction)action
- conversation:(LCIMConversation * _Nullable)conversation
- clientIds:(NSArray * _Nullable)clientIds
-signatureHandler:(void (^)(LCIMSignature * _Nullable))handler
-{
- if ([action isEqualToString:LCIMSignatureActionOpen]) {
- // For modules with signing enabled, return the corresponding signature
- LCIMSignature *signature;
- /*
- ...
- ...
- See "Demo for Generating Signatures on Cloud Engine"
- */
- handler(signature);
- } else {
- // For modules with signing disabled, return nil
- handler(nil);
- }
-}
-
-// Set the protocol delegator
-NSError *error;
-LCIMClient *imClient = [[LCIMClient alloc] initWithClientId:@"Tom" error:&error];
-if (!error) {
- imClient.signatureDataSource = signatureDelegator;
-}
-```
-
-```js
-// A Cloud Engine-based signature factory for signing requests for logging in
-var signatureFactory = function (clientId) {
- return AV.Cloud.rpc("sign", { clientId: clientId }); // AV.Cloud.rpc returns a Promise
-};
-// A Cloud Engine-based signature factory for signing requests for creating conversations, joining conversations, inviting users, and removing users
-var conversationSignatureFactory = function (
- conversationId,
- clientId,
- targetIds,
- action
-) {
- return AV.Cloud.rpc("sign-conversation", {
- conversationId: conversationId,
- clientId: clientId,
- targetIds: targetIds,
- action: action,
- });
-};
-
-realtime
- .createIMClient("Tom", {
- signatureFactory: signatureFactory,
- conversationSignatureFactory: conversationSignatureFactory,
- })
- .then(function (tom) {
- console.log("Tom logged in.");
- })
- .catch(function (error) {
- // Errors thrown by signatureFactory or for invalid signatures will be caught here
- });
-```
-
-```swift
-class SignatureDelegator: IMSignatureDelegate {
-
- // A Cloud Engine-based function for getting signatures for logging in
- func getClientOpenSignature(completion: (IMSignature) -> Void) {
- // See "Demo for Generating Signatures on Cloud Engine"
- }
-
- func client(_ client: IMClient, action: IMSignature.Action, signatureHandler: @escaping (IMClient, IMSignature?) -> Void) {
- switch action {
- case .open:
- // For modules with signing enabled, return the corresponding signature
- self.getClientOpenSignature { (signature) in
- signatureHandler(client, signature)
- }
- default:
- // For modules with signing disabled, return nil
- signatureHandler(client, nil)
- }
- }
-}
-
-do {
- let signatureDelegator = SignatureDelegator()
- let client = try IMClient(ID: "Tom", signatureDelegate: signatureDelegator)
-} catch {
- print(error)
-}
-```
-
-```dart
-
-```
-
-
-
-You should never perform signing using your Master Key on the client side. If your Master Key gets leaked, the data in your app would be accessible by anyone who has the key. Therefore, we highly recommend that you host the signing program on a server that is well-secured (like Cloud Engine).
-
-### Signing Mechanism for `User`
-
-`User` is the built-in account system coming with Data Storage. If your users have their accounts signed up or logged in with `User`, they can skip the signing process when logging in to Instant Messaging. The code below shows how a user can log in to Instant Messaging with `User`:
-
-
-
-```cs
-LCUser user = await LCUser.Login("username", "password");
-CIMClient client = new LCIMClient(user);
-await client.Open();
-```
-
-```java
-// Log in to the account system with the username and password of an LCUser
-LCUser.logInInBackground("username", "password", new LogInCallback() {
- @Override
- public void done(LCUser user, LCException e) {
- if (null != e) {
- return;
- }
- // Create a client with the LCUser instance
- LCIMClient client = LCIMClient.getInstance(user);
- // Log in to Instant Messaging
- client.open(new LCIMClientCallback() {
- @Override
- public void done(final LCIMClient avimClient, LCIMException e) {
- // Do something as you need
- }
- });
- }
-});
-```
-
-```objc
-// Log in to the account system with the username and password of an LCUser
-[LCUser logInWithUsernameInBackground:username password:password block:^(LCUser * _Nullable user, NSError * _Nullable error) {
- // Create a client with the LCUser instance
- NSError *err;
- LCIMClient *client = [[LCIMClient alloc] initWithUser:user error:&err];
- if (!err) {
- // Log in to Instant Messaging
- [client openWithCallback:^(BOOL succeeded, NSError * _Nullable error) {
- // Do something as you need
- }];
- }
-}];
-```
-
-```js
-var AV = require("leancloud-storage");
-// Log in to the account system with the username and password of an LCUser
-AV.User.logIn("username", "password")
- .then(function (user) {
- // Log in to Instant Messaging with the LCUser instance
- return realtime.createIMClient(user);
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-_ = LCUser.logIn(username: "username", password: "password") { (result) in
- switch result {
- case .success(object: let user):
- do {
- let client = try IMClient(user: user)
- client.open(completion: { (result) in
- // Do something as you need
- })
- } catch {
- print(error)
- }
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-```dart
-// Not supported yet
-```
-
-
-
-When creating `IMClient` with an `LCUser` instance that has completed the `logIn` process, the user's signature information can be directly accessed by Instant Messaging from the account system. This allows Instant Messaging to automatically verify the client being logged in and the process of applying for signatures from the third-party server can be skipped.
-
-Once `IMClient` is logged in, all the other features work in the same way as discussed earlier.
-
-## Chat Rooms
-
-We have compared different types of scenarios and conversations in our service overview. Now let's learn how to build a chat room.
-
-### Creating Chat Rooms
-
-`IMClient` has the `createChatRoom` method for creating chat rooms:
-
-
-
-```cs
-// Pass in the name of the chat room
-tom.CreateChatRoom("Chat Room");
-```
-
-```java
-tom.createChatRoom("Chat Room", null,
- new LCIMConversationCreatedCallback() {
- @Override
- public void done(LCIMConversation conv, LCIMException e) {
- if (e == null) {
- // Chat room created
- }
- }
-});
-```
-
-```objc
-[client createChatRoomWithCallback:^(LCIMChatRoom * _Nullable chatRoom, NSError * _Nullable error) {
- if (chatRoom && !error) {
- LCIMTextMessage *textMessage = [LCIMTextMessage messageWithText:@"This is a message." attributes:nil];
- [chatRoom sendMessage:textMessage callback:^(BOOL success, NSError *error) {
- if (success && !error) {
-
- }
- }];
- }
-}];
-```
-
-```js
-tom.createChatRoom({ name: "Chat Room" }).catch(console.error);
-```
-
-```swift
-do {
- try client.createChatRoom(name: "Chat Room", attributes: nil) { (result) in
- switch result {
- case .success(value: let chatRoom):
- print(chatRoom)
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-ChatRoom chatRoom = await jerry.createChatRoom(name: 'Chat Room');
-```
-
-
-
-When creating a chat room, you can specify its name and optional attributes. The interface for creating chat rooms has the following differences compared to that for creating basic conversations:
-
-- A chat room doesn't have a member list, so there is no need to specify `members`.
-- For the same reason, there is no need to specify `unique` (the cloud doesn't need to merge conversations by member lists).
-
-> Although it's possible to create a chat room by passing `{ transient: true }` into `createConversation`, we still recommend that you use `createChatRoom` directly.
-
-### Finding Chat Rooms
-
-In the first chapter, we have discussed how you can use `ConversationsQuery` to look for conversations with your custom conditions. This works for chat rooms as well, as long as you add `transient = true` as a constraint.
-
-
-
-```cs
-LCIMConversationQuery query = new LCIMConversationQuery(tom);
-query.WhereEqualTo("tr", true);
-```
-
-```java
-LCIMConversationsQuery query = tom.getChatRoomQuery();
-query.findInBackground(new LCIMConversationQueryCallback() {
- @Override
- public void done(List conversations, LCIMException e) {
- if (null == e) {
- // Success
- } else {
- // Error handling
- }
- }
-});
-```
-
-```objc
-LCIMConversationQuery *query = [tom conversationQuery];
-[query whereKey:@"tr" equalTo:@(YES)];
-```
-
-```js
-var query = tom.getQuery().equalTo("tr", true); // Restrict to chat rooms
-query
- .find()
- .then(function (conversations) {
- // conversations contains all the results
- })
- .catch(console.error);
-```
-
-```swift
-do {
- let query = client.conversationQuery
- try query.where("tr", .equalTo(true))
- try query.findConversations { (result) in
- switch result {
- case .success(value: let conversations):
- guard conversations is [IMChatRoom] else {
- return
- }
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- ConversationQuery query = tom.conversationQuery();
- query.whereEqualTo('tr', true);
- // conversations contains all the results
- List conversations = await query.find();
-} catch (e) {
- print(e);
-}
-```
-
-
-
-> The Java (Android) SDK offers the `LCIMClient#getChatRoomQuery` method that is dedicated to querying chat rooms. By using this method, you won't need to deal with the `transient` attribute of conversations.
-
-### Joining and Leaving Chat Rooms
-
-When coming to the interfaces for joining or leaving conversations, group chats are the same as basic conversations. See [Group Chats](/sdk/im/guide/beginner/) in the first chapter for more details.
-
-However, there are several differences in the ways members are managed and notifications are delivered:
-
-- A user cannot be invited to or removed from a chat room. They are only able to join or leave on their own.
-- If a user logs out, this user will be automatically removed from the chat room they are already in. An exception is that if the user gets offline unexpectedly, they will be added back to the chat room they are previously in as long as they get back within 30 minutes.
-- The cloud will not deliver notifications for users joining or leaving chat rooms.
-- The list of members in a chat room cannot be retrieved. Only the count of members is available.
-
-As a side note, functions like **push notifications, message synchronization, and receipts are also not supported by chat rooms**.
-
-### Getting Member Counts
-
-The `LCIMConversation#memberCount` method lets you get the count of members in a conversation. When used on a chat room, you get the number of people in it at that moment:
-
-
-
-```cs
-int membersCount = await conversation.GetMembersCount();
-```
-
-```java
-private void TomQueryWithLimit() {
- LCIMClient tom = LCIMClient.getInstance("Tom");
- tom.open(new LCIMClientCallback() {
-
- @Override
- public void done(LCIMClient client, LCIMException e) {
- if (e == null) {
- // Successfully logged in
- LCIMConversationsQuery query = tom.getConversationsQuery();
- query.setLimit(1);
- // Get the first conversation
- query.findInBackground(new LCIMConversationQueryCallback() {
- @Override
- public void done(List convs, LCIMException e) {
- if (e == null) {
- if (convs != null && !convs.isEmpty()) {
- LCIMConversation conv = convs.get(0);
- // Get the count of people in the first conversation
- conv.getMemberCount(new LCIMConversationMemberCountCallback() {
-
- @Override
- public void done(Integer count, LCIMException e) {
- if (e == null) {
- Log.d("Tom & Jerry has " + count + " people online");
- }
- }
- });
- }
- }
- }
- });
- }
- }
- });
-}
-```
-
-```objc
-// Get the count of people
-[conversation countMembersWithCallback:^(NSInteger number, NSError *error) {
- NSLog(@"%ld",number);
-}];
-```
-
-```js
-chatRoom
- .count()
- .then(function (count) {
- console.log("Count: " + count);
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-do {
- chatRoom.getOnlineMembersCount { (result) in
- switch result {
- case .success(count: let count):
- print(count)
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-int count = await chatRoom.countMembers();
-```
-
-
-
-### Message Priorities
-
-To ensure that important messages get delivered promptly, the server would selectively discard a certain amount of messages with lower priorities when the network connection is bad. Below are the priorities supported:
-
-| Priority | Description |
-| ------------------------ | -------------------------------------------------------------------- |
-| `MessagePriority.HIGH` | High priority. Used for messages that need to be delivered promptly. |
-| `MessagePriority.NORMAL` | Normal priority. Used for ordinary text messages. |
-| `MessagePriority.LOW` | Low priority. Used for messages that are less important. |
-
-The default priority is `NORMAL`.
-
-The priority of a message can be set when sending the message. The code below shows how you can send a message with high priority:
-
-
-
-```cs
-LCIMTextMessage message = new LCIMTextMessage("The score is still 0:0. China definitely needs a substitution for the second half.");
-LCIMMessageSendOptions options = new LCIMMessageSendOptions {
- Priority = LCIMMessagePriority.High
-};
-await chatRoom.Send(message, options);
-```
-
-```java
-LCIMClient tom = LCIMClient.getInstance("Tom");
- tom.open(new LCIMClientCallback() {
- @Override
- public void done(LCIMClient client, LCIMException e) {
- if (e == null) {
- // Create a conversation named "Tom & Jerry"
- client.createConversation(Arrays.asList("Jerry"), "Tom & Jerry", null,
- new LCIMConversationCreatedCallback() {
- @Override
- public void done(LCIMConversation conv, LCIMException e) {
- if (e == null) {
- LCIMTextMessage msg = new LCIMTextMessage();
- msg.setText("Get up, Jerry!");
-
- LCIMMessageOption messageOption = new LCIMMessageOption();
- messageOption.setPriority(LCIMMessageOption.MessagePriority.High);
- conv.sendMessage(msg, messageOption, new LCIMConversationCallback() {
- @Override
- public void done(LCIMException e) {
- if (e == null) {
- // Sent
- }
- }
- });
- }
- }
- });
- }
- }
- });
-```
-
-```objc
-LCIMMessageOption *option = [[LCIMMessageOption alloc] init];
-option.priority = LCIMMessagePriorityHigh;
-[chatRoom sendMessage:[LCIMTextMessage messageWithText:@"Get up, Jerry!" attributes:nil] option:option callback:^(BOOL succeeded, NSError * _Nullable error) {
- // Things to do after the message is sent
-}];
-```
-
-```js
-var { Realtime, TextMessage, MessagePriority } = require("leancloud-realtime");
-var realtime = new Realtime({
- appId: "GDBz24d615WLO5e3OM3QFOaV-gzGzoHsz",
- appKey: "dlCDCOvzMnkXdh2czvlbu3Pk",
-});
-realtime
- .createIMClient("host")
- .then(function (host) {
- return host.createConversation({
- members: ["broadcast"],
- name: "2094 FIFA World Cup - Vatican City vs China",
- transient: true,
- });
- })
- .then(function (conversation) {
- console.log(conversation.id);
- return conversation.send(
- new TextMessage(
- "The score is still 0:0. China definitely needs a substitution for the second half."
- ),
- { priority: MessagePriority.HIGH }
- );
- })
- .then(function (message) {
- console.log(message);
- })
- .catch(console.error);
-```
-
-```swift
-do {
- let message = IMTextMessage(text: "The score is still 0:0. China definitely needs a substitution for the second half.")
- try chatRoom.send(message: message, priority: .high) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-try {
- TextMessage message = TextMessage();
- message.text = 'The score is still 0:0. China definitely needs a substitution for the second half.';
- await chatRoom.send(message: message, priority: MessagePriority.high);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-> Note:
->
-> This feature is only available for _chat rooms_. There won't be an effect if you set priorities for messages in basic conversations, since these messages will never get discarded.
-
-### Muting Conversations
-
-If a user doesn't want to get notifications for new messages in a conversation but still wants to stay in the conversation, they can mute the conversation.
-
-For example, Tom is getting busy and wants to mute a conversation:
-
-
-
-```cs
-await chatRoom.Mute();
-```
-
-```java
-LCIMClient tom = LCIMClient.getInstance("Tom");
-tom.open(new LCIMClientCallback(){
-
- @Override
- public void done(LCIMClient client,LCIMException e){
- if(e==null){
- // Logged in
- LCIMConversation conv = client.getConversation("551260efe4b01608686c3e0f");
- conv.mute(new LCIMConversationCallback(){
-
- @Override
- public void done(LCIMException e){
- if(e==null){
- // Muted
- }
- }
- });
- }
- }
-});
-```
-
-```objc
-// Tom mutes the conversation
-[conversation muteWithCallback:^(BOOL succeeded, NSError *error) {
- if (succeeded) {
- NSLog(@"Muted!");
- }
-}];
-```
-
-```js
-tom
- .getConversation("CONVERSATION_ID")
- .then(function (conversation) {
- return conversation.mute();
- })
- .then(function (conversation) {
- console.log("Muted!");
- })
- .catch(console.error.bind(console));
-```
-
-```swift
-conversation.mute { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-```dart
-await chatRoom.mute();
-```
-
-
-
-After a conversation is muted, the current user will not get push notifications from it anymore. To unmute a conversation, use `Conversation#unmute`.
-
-> Tips:
->
-> - Both chat rooms and basic conversations can be muted.
-> - `mute` and `unmute` operations will change the `mu` field in the `_Conversation` class. **Do not change the `mu` field directly in your app's dashboard**, otherwise push notifications may not work properly.
-
-### Text Moderation
-
-
-
-You might consider filtering cuss words out from the messages sent into group chats by users. Instant Messaging offers a built-in text moderation function that supports this need. It works for group chats, chat rooms, and system conversations by default. You can also enable it for one-on-one chats by going to **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings**.
-
-
-
-
-
-Instant Messaging offers a built-in text moderation function that allows you to filter cuss words out from the messages sent by users. You can enable it for one-on-one chats by going to **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings**.
-
-
-
-Matched keywords will be replaced with `***`.
-
-Text moderation will lead to message modifications at the system level, so the message sender will receive a `MESSAGE_UPDATE` event, which the clients can listen to. Please refer to the "Modify a Message" section of the previous chapter for code samples.
-
-
-
-Instant Messaging offers a set of keywords by default, but if you have upgraded to the Business Plan, you can customize the keywords. To do so, go to **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings** and upload your keywords file to replace the default list. The uploaded file must be UTF-8 encoded with one keyword in each line. If you upload your custom word list, it will override the default one.
-
-
-
-
-
-If you have upgraded to the Business Plan, you can customize the keywords. To do so, go to **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings** and upload your keywords file. The uploaded file must be UTF-8 encoded with one keyword in each line. For now, the **Enable sensitive keyword filtering against group chats** option on **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings** is always enabled, but if you don't upload a file of keywords, the text moderation function will not function in the end.
-
-
-
-Filtering rules for sensitive words: If the message is a rich media message type (the `_lctype` attribute has a value), only the contents of the `_lctext` field are filtered. If the message is not a rich media message type (the message does not have the `_lctype` attribute), then the entire message body is filtered.
-
-If you have more complicated requirements regarding text moderation, we recommend that you make use of the `_messageReceived` hook of Cloud Engine. You can define your own logic for controlling messages.
-
-## Temporary Conversations
-
-Temporary conversations can be used for special scenarios with:
-
-- Short TTL
-- Fewer members (10 `clientId`s maximum)
-- No message history needed
-
-What makes temporary conversations different from other conversations is that they **expire very quickly**. This helps you reduce the space needed for storing conversations and lower the cost of maintaining your app. Temporary conversations are best used for customer service systems.
-
-### Creating Temporary Conversations
-
-`IMConversation` has its `createTemporaryConversation` method for creating temporary conversations:
-
-
-
-```cs
-LCIMTemporaryConversation temporaryConversation = await tom.CreateTemporaryConversation(new string[] { "Jerry", "William" });
-```
-
-```java
-tom.createTemporaryConversation(Arrays.asList(members), 3600, new LCIMConversationCreatedCallback(){
- @Override
- public void done(LCIMConversation conversation, LCIMException e) {
- if (null == e) {
- LCIMTextMessage msg = new LCIMTextMessage();
- msg.setText("This is a temporary conversation.");
- conversation.sendMessage(msg, new LCIMConversationCallback(){
- @Override
- public void done(LCIMException e) {
- }
- });
- }
- }
-});
-```
-
-```objc
-[self createTemporaryConversationWithClientIds:@[@"Jerry", @"William"] callback:^(LCIMTemporaryConversation * _Nullable temporaryConversation, NSError * _Nullable error) {
- if (temporaryConversation) {
- // success
- }
-}];
-```
-
-```js
-realtime
- .createIMClient("Tom")
- .then(function (tom) {
- return tom.createTemporaryConversation({
- members: ["Jerry", "William"],
- });
- })
- .then(function (conversation) {
- return conversation.send(
- new AV.TextMessage("This is a temporary conversation.")
- );
- })
- .catch(console.error);
-```
-
-```swift
-do {
- try client.createTemporaryConversation(clientIDs: ["Jerry", "William"], timeToLive: 3600) { (result) in
- switch result {
- case .success(value: let tempConversation):
- print(tempConversation)
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-TemporaryConversation temporaryConversation;
-try {
- temporaryConversation = await jerry.createTemporaryConversation(
- members: {'Jerry', 'William'},
- );
-} catch (e) {
- print(e);
-}
-
-try {
- TextMessage message = TextMessage();
- message.text = 'This is a temporary conversation.';
- await temporaryConversation.send(message: message);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-Temporary conversations have an **important** attribute that differentiates them from others: TTL. It is set to 1 day by default, but you can change it to any time no longer than 30 days. If you want a conversation to survive for more than 30 days, make it a basic conversation instead. The code below creates a temporary conversation with a custom TTL:
-
-
-
-```cs
-LCIMTemporaryConversation temporaryConversation = await tom.CreateTemporaryConversation(new string[] { "Jerry", "William" },
- ttl: 3600);
-```
-
-```java
-LCIMClient client = LCIMClient.getInstance("Tom");
-client.open(new LCIMClientCallback() {
- @Override
- public void done(LCIMClient avimClient, LCIMException e) {
- if (null == e) {
- String[] members = {"Jerry", "William"};
- avimClient.createTemporaryConversation(Arrays.asList(members), 3600, new LCIMConversationCreatedCallback(){
- @Override
- public void done(LCIMConversation conversation, LCIMException e) {
- if (null == e) {
- LCIMTextMessage msg = new LCIMTextMessage();
- msg.setText("This is a temporary conversation. It will expire in 1 hour.");
- conversation.sendMessage(msg, new LCIMConversationCallback(){
- @Override
- public void done(LCIMException e) {
- }
- });
- }
- }
- });
- }
- }
-});
-```
-
-```objc
-LCIMConversationCreationOption *option = [LCIMConversationCreationOption new];
-option.timeToLive = 3600;
-[self createTemporaryConversationWithClientIds:@[@"Jerry", @"William"] option:option callback:^(LCIMTemporaryConversation * _Nullable temporaryConversation, NSError * _Nullable error) {
- if (temporaryConversation) {
- // success
- }
-}];
-```
-
-```js
-realtime
- .createIMClient("Tom")
- .then(function (tom) {
- return tom.createTemporaryConversation({
- members: ["Jerry", "William"],
- ttl: 3600,
- });
- })
- .then(function (conversation) {
- return conversation.send(
- new AV.TextMessage(
- "This is a temporary conversation. It will expire in 1 hour."
- )
- );
- })
- .catch(console.error);
-```
-
-```swift
-do {
- try client.createTemporaryConversation(clientIDs: ["Jerry", "William"], timeToLive: 3600) { (result) in
- switch result {
- case .success(value: let tempConversation):
- print(tempConversation)
- case .failure(error: let error):
- print(error)
- }
- }
-} catch {
- print(error)
-}
-```
-
-```dart
-TemporaryConversation temporaryConversation;
-try {
- temporaryConversation = await jerry.createTemporaryConversation(
- members: {'Jerry', 'William'},
- timeToLive: 3600,
- );
-} catch (e) {
- print(e);
-}
-
-try {
- TextMessage message = TextMessage();
- message.text = 'This is a temporary conversation. It will expire in 1 hour.';
- await temporaryConversation.send(message: message);
-} catch (e) {
- print(e);
-}
-```
-
-
-
-Besides this, a temporary conversation shares the same functionality as a basic conversation.
-
-## Continue Reading
-
-- [4. Hooks and System Conversations](/sdk/im/guide/systemconv/)
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/systemconv.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/systemconv.mdx
deleted file mode 100644
index 24817c77d..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/guide/systemconv.mdx
+++ /dev/null
@@ -1,1428 +0,0 @@
----
-title: 4. Hooks and System Conversations
-sidebar_label: Hooks and System Conversations
-sidebar_position: 4
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import Mermaid from "/src/docComponents/Mermaid";
-
-## Introduction
-
-In the previous chapter, [Security, Chat Rooms, and Temporary Conversations](/sdk/im/guide/senior/), we introduced our third-party signing mechanism. In this chapter, we will cover the following functionalities offered by the Instant Messaging service:
-
-- Hooks
-- System conversations
-
-## Hooks
-
-Instant Messaging is built with an open architecture that has strong extensibility, allowing you to do more than implement basic chatting features. Instant Messaging provides a collection of hooks that make it handy for you to utilize such extensibility. We’ll delve into them later in this section.
-
-### The Connection Between Hooks and Instant Messaging
-
-Hooks are a special message-handling mechanism for your app to intercept and process the various types of events and messages sent through it. They allow you to trigger custom logics when these events and messages are sent, enabling you to extend the existing features provided by the Instant Messaging service.
-
-Take **\_messageRecieved** as an example. This hook gets triggered when a message arrives at the server. Within the hook, you can obtain the properties of the message including its content, sender, and receivers. All these properties can be modified within the hook before they get taken over by the server. The server will then complete the delivery of the message with the modified properties and the message seen by the receivers will be the one with the modified properties instead of the original message. The hook can also reject the message so that the message won’t be seen by the receivers anymore.
-
-**Keep in mind that by default, if a hook fails due to timing out or returning a non-200 status code, the server will disregard the failure and continue processing the original request.** You can change this behavior by enabling **Return error and stop processing request when hook failed** under **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Instant Messaging settings**. With this option enabled, if a hook fails, the server will return the error message to the client and abort the request.
-
-### Hooks for Messages
-
-After being sent out from the sender and before being received by the receivers, a message would go through a series of stages determined by the online statuses of the receivers. You can set up a hook that gets triggered for each of the stages:
-
-- **\_messageReceived**
- This hook gets triggered after the server receives a message and parses the members in the group, but before the message gets delivered to the receivers. Here you can perform operations like modifying the message’s content and receivers.
-- **\_messageSent**
- This hook gets triggered after a message gets delivered. Here you can perform operations like logging and making a copy of the message on your backup server.
-- **\_receiversOffline**
- This hook gets triggered after a message gets delivered with some of the receivers offline, but before push notifications get sent to the offline receivers. Here you can perform operations like dynamically updating the content and device list of the push notifications.
-- **\_messageUpdate**
- This hook gets triggered after the server receives a request for updating a message, but before the updated message gets delivered to the receivers. Similar to the situation when a new message is sent, here you can perform operations like modifying the message’s content and receivers.
-
-### Hooks for Conversations
-
-A hook can be triggered before or after a conversation-related operation takes place, like when a conversation gets created or when the member list of a conversation gets updated:
-
-- **\_conversationStart**
- When a conversation is being created, this hook gets triggered after the signature validation (if enabled) has been completed but before the conversation actually gets created. Here you can perform operations like adding additional internal attributes to the conversation and performing authentication.
-- **\_conversationStarted**
- This hook gets triggered after a conversation gets created. Here you can perform operations like logging and making a copy of the conversation on your backup server.
-- **\_conversationAdd**
- When a member is joining or being added to a conversation, this hook gets triggered after the signature validation (if enabled) has been completed but before the member actually joins or gets added to the conversation. Here you can perform operations like determining whether the request shall be accepted or declined.
-- **\_conversationRemove**
- When a member is being removed from a conversation, this hook gets triggered after the signature validation (if enabled) has been completed but before the member actually gets removed from the conversation. This hook doesn’t get triggered when a member is leaving a conversation. Here you can perform operations like determining whether the request shall be accepted or declined.
-- **\_conversationAdded**
- This hook gets triggered after a user successfully joins a conversation.
-- **\_conversationRemoved**
- This hook gets triggered after a user successfully leaves a conversation.
-- **\_conversationUpdate**
- When a conversation’s name, custom attributes, or notification settings are being updated, this hook gets triggered before the update actually takes place. Here you can perform operations like adding additional internal attributes to the conversation and performing authentication.
-
-### Hooks for Client Status Changes
-
-A hook can be triggered when a client logs in or logs out:
-
-- **\_clientOnline**
- This hook gets triggered when a client logs in successfully.
-- **\_clientOffline**
- This hook gets triggered when a client logs out successfully or loses connection unexpectedly.
-
-You can use these hooks together with LeanCache to implement an endpoint for looking up the online statuses of clients.
-
-### Hooks and Cloud Engine
-
-To maintain the necessary performance for handling an abundance of messages, the Instant Messaging service itself doesn’t provide the computing resources for running hooks. In order to use hooks, you will have to set up Cloud Engine instances for your application and deploy hooks onto these instances.
-
-The hooks for Instant Messaging will only take effect when deployed to the **production environment** of Cloud Engine. The staging environment shall be used for testing hooks but the hooks deployed there can only be triggered manually. Due to the existence of the cache, it may take up to 3 minutes for hooks to take effect if you’re deploying hooks to Cloud Engine for the first time. After that, the hooks deployed will take effect immediately.
-
-### Hooks API
-
-Conversation-related hooks can be used to perform additional permission checks besides those taken care of by the signing mechanism, controlling whether a conversation can be created or whether a user can be allowed into a conversation. One thing you can do with this hook is to implement a blocklist for your application.
-
-#### `_messageReceived`
-
-This hook gets triggered after a message arrives at the server. If the message is sent to a group, the server will parse all the receivers of the message.
-
-You can have the hook return a value to control whether the message should be discarded, which receivers should be removed, and what the updated message should be if the message is to be updated. If the hook returns an empty object (`response.success({})`), the message will go through the default workflow.
-
-If the hook contains conditionals, please be careful to **make sure the hook will always invoke `response.success` in the end to return a result** so that the message can be delivered without delay. The hook will **block the process of message delivery**, which means that you should keep the hook efficient by eliminating unnecessary invocations within the hook.
-
-For a rich media message, the `content` parameter will be a string containing a JSON object. See [Instant Messaging REST API Guide](/sdk/im/guide/rest/) for more information about the structure of this object.
-
-Parameters:
-
-| Parameter | Description |
-| ----------- | ---------------------------------------------------------------------------------- |
-| `fromPeer` | The ID of the sender. |
-| `convId` | The ID of the conversation the message belongs to. |
-| `toPeers` | The `clientId`s of the members in the conversation. |
-| `transient` | Whether this is a transient message. |
-| `bin` | Whether the content of the original message is binary. |
-| `content` | The string representing the content of the message. If `bin` is `true`, this string will be the original message encoded in Base64 format. |
-| `receipt` | Whether a receipt is requested. |
-| `timestamp` | The timestamp the server received the message (in milliseconds). |
-| `system` | Whether the message belongs to a system conversation. |
-| `sourceIP` | The IP address of the sender. |
-
-Example arguments:
-
-```json
-{
- "fromPeer": "Tom",
- "receipt": false,
- "groupId": null,
- "system": null,
- "content": "{\"_lctext\":\"Holy crap!\",\"_lctype\":-1}",
- "convId": "5789a33a1b8694ad267d8040",
- "toPeers": ["Jerry"],
- "bin": false,
- "transient": false,
- "sourceIP": "121.239.62.103",
- "timestamp": 1472200796764
-}
-```
-
-Return values:
-
-| Parameter | Constraint | Description |
-| --------- | ---- | --------------------------------------------------------------------------------------------------------------------------- |
-| `drop` | Optional | The message will be discarded if this value is `true`. |
-| `code` | Optional | A custom error code (integer) to be returned when `drop` is `true`. |
-| `detail` | Optional | A custom error message (string) to be returned when `drop` is `true`. |
-| `bin` | Optional | Whether the returned `content` is binary. If omitted, this will be the same as the value of `bin` in the request. |
-| `content` | Optional | The updated `content`. If omitted, the original message content will be used. If `bin` is `true`, this should be the message encoded in Base64 format. |
-| `toPeers` | Optional | An array containing the updated receivers. If omitted, the original receivers will be used. |
-
-Code example:
-
-
-
-```js
-AV.Cloud.onIMMessageReceived((request) => {
- let content = request.params.content;
- let processedContent = content.replace("crap", "**");
- // Must provide a return value, or an error will occur
- return {
- content: processedContent,
- };
-});
-```
-
-```python
-import json
-
-@engine.define
-def _messageReceived(**params):
- content = json.loads(params['content'])
- text = content['_lctext']
- content['_lctext'] = text.replace('crap', '**')
- # Must provide a return value, or an error will occur
- return {
- 'content': json.dumps(content)
- }
-```
-
-```php
-Cloud::define("_messageReceived", function($params, $user) {
- $content = json_decode($params["content"], true);
- $text = $content["_lctext"];
- $content["_lctext"] = preg_replace("crap", "**", $text);
- // Must provide a return value, or an error will occur
- return array("content" => json_encode($content));
-});
-```
-
-```java
-@IMHook(type = IMHookType.messageReceived)
- public static Map onMessageReceived(Map params) {
- Map result = new HashMap();
- String content = (String)params.get("content");
- String processedContent = content.replace("crap", "**");
- result.put("content", processedContent);
- // Must provide a return value, or an error will occur
- return result;
- }
-```
-
-```cs
-[LCEngineRealtimeHook(LCEngineRealtimeHookType.MessageReceived)]
-public static object OnMessageReceived(Dictionary parameters) {
- string content = parameters["content"] as string;
- string processedContent = content.Replace("crap", "**");
- return new Dictionary {
- { "content", processedContent }
- };
-}
-```
-
-```go
-// Not supported yet
-```
-
-
-
-With the code above enabled, the sequence diagram of a message will be:
-
->RTM: 1. Send a message
-RTM-->>Engine: 2. Trigger the _messageReceived hook
-Engine-->>RTM: 3. Return the result of the hook
-RTM-->>SDK: 4. Send the result of the hook to the receiver
-`}
-/>
-
-- The diagram above assumes that all the members in the conversation are online. The sequence will be slightly different if some of the members are offline. We will talk about this in the next section.
-- _RTM_ refers to the cluster for the Instant Messaging service and _Engine_ refers to that for the Cloud Engine service. These two clusters communicate through our internal network.
-
-#### `_receiversOffline`
-
-This hook gets triggered when some of the receivers are offline. A common use case of this hook is to customize the content and receivers of push notifications. You can even trigger custom push notifications with this hook. Keep in mind that messages sent to chat rooms won’t trigger this hook.
-
-Parameters:
-
-| Parameter | Description |
-| --------------------- | --------------------------------------------------------------------------------------------------------------------- |
-| `fromPeer` | The ID of the sender. |
-| `convId` | The ID of the conversation the message belongs to. |
-| `offlinePeers` | An array containing the receivers that are offline. |
-| `content` | The content of the message. |
-| `timestamp` | The timestamp the server received the message (in milliseconds). |
-| `mentionAll` | A boolean indicating whether all the members are mentioned. |
-| `mentionOfflinePeers` | Members who are offline but got mentioned by this message. If `mentionAll` is `true`, this parameter will be empty, indicating that all the members in `offlinePeers` are mentioned. |
-
-Return values:
-
-| Parameter | Constraint | Description |
-| -------------- | ---- | -------------------------------------------------------------------- |
-| `skip` | Optional | If set to `true`, push notifications will be skipped. This could be useful if you have already triggered push notifications in a different manner. |
-| `offlinePeers` | Optional | An array containing the updated receivers. |
-| `pushMessage` | Optional | The content of the push notifications. You can provide a JSON object with a custom structure. |
-| `force` | Optional | If set to `true`, push notifications will be sent to the users in `offlinePeers` who `mute`d the conversation. Defaults to `false`. |
-
-Code example:
-
-
-
-```js
-AV.Cloud.onIMReceiversOffline((request) => {
- let params = request.params;
- let content = params.content;
-
- // params.content is the content of the message
- let shortContent = content;
-
- if (shortContent.length > 6) {
- shortContent = content.slice(0, 6);
- }
-
- console.log("shortContent", shortContent);
-
- return {
- pushMessage: JSON.stringify({
- // Increment the number of unread messages; you can provide a number as well
- badge: "Increment",
- sound: "default",
- // Use the dev certificate
- _profile: "dev",
- alert: shortContent,
- }),
- };
-});
-```
-
-```python
-@engine.define
-def _receiversOffline(**params):
- print('_receiversOffline start')
- # params['content'] is the content of the message
- content = params['content']
- short_content = content[:6]
- print('short_content:', short_content)
- payloads = {
- # Increment the number of unread messages; you can provide a number as well
- 'badge': 'Increment',
- 'sound': 'default',
- # Use the dev certificate
- '_profile': 'dev',
- 'alert': short_content,
- }
- print('_receiversOffline end')
- return {
- 'pushMessage': json.dumps(payloads),
- }
-```
-
-```php
-Cloud::define('_receiversOffline', function($params, $user) {
- error_log('_receiversOffline start');
- // content is the content of the message
- $shortContent = $params["content"];
- if (strlen($shortContent) > 6) {
- $shortContent = substr($shortContent, 0, 6);
- }
-
- $json = array(
- // Increment the number of unread messages; you can provide a number as well
- "badge" => "Increment",
- "sound" => "default",
- // Use the dev certificate
- "_profile" => "dev",
- "alert" => shortContent
- );
-
- $pushMessage = json_encode($json);
- return array(
- "pushMessage" => $pushMessage,
- );
-});
-```
-
-```java
-@IMHook(type = IMHookType.receiversOffline)
- public static Map onReceiversOffline(Map params) {
- // content is the content of the message
- String alert = (String)params.get("content");
- if(alert.length() > 6){
- alert = alert.substring(0, 6);
- }
- System.out.println(alert);
- Map result = new HashMap();
- JSONObject object = new JSONObject();
- // Increment the number of unread messages
- // You can provide a number as well
- object.put("badge", "Increment");
- object.put("sound", "default");
- // Use the dev certificate
- object.put("_profile", "dev");
- object.put("alert", alert);
- result.put("pushMessage", object.toString());
- return result;
-}
-```
-
-```cs
-[LCEngineRealtimeHook(LCEngineRealtimeHookType.ReceiversOffline)]
-public static Dictionary OnReceiversOffline(Dictionary parameters) {
- string alert = parameters["content"] as string;
- if (alert.Length > 6) {
- alert = alert.Substring(0, 6);
- }
- Dictionary pushMessage = new Dictionary {
- { "badge", "Increment" },
- { "sound", "default" },
- { "_profile", "dev" },
- { "alert", alert },
- };
- return new Dictionary {
- { "pushMessage", JsonSerializer.Serialize(pushMessage) }
- };
-}
-```
-
-```go
-// Not supported yet
-```
-
-
-
-#### `_messageSent`
-
-This hook gets triggered after a message gets delivered. It won’t impact the performance of the message-delivery process, so you can leave time-consuming operations here.
-
-Parameters:
-
-| Parameter | Description |
-| -------------- | -------------------------------- |
-| `fromPeer` | The ID of the sender. |
-| `convId` | The ID of the conversation the message belongs to. |
-| `msgId` | The ID of the message. |
-| `onlinePeers` | The list of the online users’ IDs. |
-| `offlinePeers` | The list of the offline users’ IDs. |
-| `transient` | Whether this is a transient message. |
-| `system` | Whether the message belongs to a system conversation. |
-| `bin` | Whether this is a binary message. |
-| `content` | The string representing the content of the message. |
-| `receipt` | Whether a receipt is requested. |
-| `timestamp` | The timestamp the server received the message (in milliseconds). |
-| `sourceIP` | The IP address of the sender. |
-
-Example arguments:
-
-```json
-{
- "fromPeer": "Tom",
- "receipt": false,
- "onlinePeers": [],
- "content": "12345678",
- "convId": "5789a33a1b8694ad267d8040",
- "msgId": "fptKnuYYQMGdiSt_Zs7zDA",
- "bin": false,
- "transient": false,
- "sourceIP": "114.219.127.186",
- "offlinePeers": ["Jerry"],
- "timestamp": 1472703266522
-}
-```
-
-Return values:
-
-The return value of this hook won’t be checked. You can just have the hook return `{}`.
-
-Code example:
-
-The code below shows how you can have a log printed to Cloud Engine when a message gets delivered:
-
-
-
-```js
-AV.Cloud.onIMMessageSent((request) => {
- console.log("params", request.params);
-});
-```
-
-```python
-@engine.define
-def _messageSent(**params):
- print('_messageSent start')
- print('params:', params)
- print('_messageSent end')
- return {}
-```
-
-```php
-Cloud::define('_messageSent', function($params, $user) {
- error_log('_messageSent start');
- error_log('params' . json_encode($params));
- return array();
-});
-```
-
-```java
-@IMHook(type = IMHookType.messageSent)
- public static Map onMessageSent(Map params) {
- System.out.println(params);
- Map result = new HashMap();
- return result;
-}
-```
-
-```cs
-[LCEngineRealtimeHook(LCEngineRealtimeHookType.MessageSent)]
-public static Dictionary OnMessageSent(Dictionary parameters) {
- Console.WriteLine(JsonSerializer.Serialize(parameters));
- return default;
-}
-```
-
-```go
-// Not supported yet
-```
-
-
-
-#### `_messageUpdate`
-
-This hook gets triggered after the server receives a request for updating a message, but before the updated message gets delivered to the receivers.
-
-You can have the hook return a value to control whether the request for updating the message should be discarded, which receivers should be removed, and what the updated message should be if the message is to be updated again.
-
-If the hook contains conditionals, please be careful to **make sure the hook will always invoke `response.success` in the end to return a result** so that the updated message can be delivered without delay. The hook will **block the process of message delivery**, which means that you should keep the hook efficient by eliminating unnecessary invocations within the hook.
-
-For a rich media message, the `content` parameter will be a string containing a JSON object. See [Instant Messaging REST API Guide](/sdk/im/guide/rest/) for more information about the structure of this object.
-
-Parameters:
-
-| Parameter | Description |
-| ----------- | ---------------------------------------------------------------------------------- |
-| `fromPeer` | The ID of the sender. |
-| `convId` | The ID of the conversation the message belongs to. |
-| `toPeers` |The `clientId`s of the members in the conversation. |
-| `bin` | Whether the content of the original message is binary. |
-| `content` | The string representing the content of the message. If `bin` is `true`, this string will be the original message encoded in Base64 format. |
-| `timestamp` | The timestamp the server received the message (in milliseconds). |
-| `msgId` | The ID of the message being updated. |
-| `sourceIP` | The IP address of the sender. |
-| `recall` | Whether the message is recalled. |
-| `system` | Whether the message belongs to a system conversation. |
-
-Return values:
-
-| Parameter | Constraint | Description |
-| --------- | ---- | --------------------------------------------------------------------------------------------------------------------------- |
-| `drop` | Optional | The request for updating the message will be discarded if this value is `true`. |
-| `code` | Optional | A custom error code (integer) to be returned when `drop` is `true`. |
-| `detail` | Optional | A custom error message (string) to be returned when `drop` is `true`. |
-| `bin` | Optional | Whether the returned `content` is binary. If omitted, this will be the same as the value of `bin` in the request. |
-| `content` | Optional | The updated `content`. If omitted, the original message content will be used. If `bin` is `true`, this should be the message encoded in Base64 format. |
-| `toPeers` | Optional | An array containing the updated receivers. If omitted, the original receivers will be used. |
-
-#### `_conversationStart`
-
-When a conversation is being created, this hook gets triggered after the signature validation (if enabled) has been completed but before the conversation actually gets created.
-
-Parameters:
-
-| Parameter | Description |
-| --------- | ---------------------------- |
-| `initBy` | The `clientId` of the initiator of the conversation. |
-| `members` | An array containing the initial members of the conversation. |
-| `attr` | Additional attributes assigned to the conversation. |
-
-Example arguments:
-
-```
-{
- "initBy": "Tom",
- "members": ["Tom", "Jerry"],
- "attr": {
- "name": "Tom & Jerry"
- }
-}
-```
-
-Return values:
-
-| Parameter | Constraint | Description |
-| -------- | ---- | ---------------------------------------------------------------- |
-| `reject` | Optional | Whether to reject the request. Defaults to `false`. |
-| `code` | Optional | A custom error code (integer) to be returned when `reject` is `true`. |
-| `detail` | Optional | A custom error message (string) to be returned when `reject` is `true`. |
-
-For example, to refuse a conversation to be created if it contains less than 4 initial members:
-
-
-
-```js
-AV.Cloud.onIMConversationStart((request) => {
- if (request.params.members.length < 4) {
- return {
- reject: true,
- code: 1234,
- detail: "Please invite at least 3 people to the conversation",
- };
- } else {
- return {};
- }
-});
-```
-
-```python
-@engine.define
-def _conversationStart(**params):
- if len(params["members"]) < 4:
- return {
- "reject": True,
- "code": 1234,
- "detail": "Please invite at least 3 people to the conversation",
- }
- else:
- return {}
-```
-
-```php
-Cloud::define('_conversationStart', function($params, $user) {
- if (count($params["members"]) < 4) {
- return [
- "reject" => true,
- "code" => 1234,
- "detail" => "Please invite at least 3 people to the conversation",
- ];
- } else {
- return array();
- }
-});
-```
-
-```java
-@IMHook(type = IMHookType.conversationStart)
-public static Map onConversationStart(Map params) {
- String[] members = (String[])params.get("members");
- Map result = new HashMap();
- if (members.length < 4) {
- result.put("reject", true);
- result.put("code", 1234);
- result.put("detail", "Please invite at least 3 people to the conversation");
- }
- return result;
-}
-```
-
-```cs
-[LCEngineRealtimeHook(LCEngineRealtimeHookType.ConversationStart)]
-public static object OnConversationStart(Dictionary parameters) {
- List
-
-#### `_conversationStarted`
-
-This hook gets triggered after a conversation gets created.
-
-Parameters:
-
-| Parameter | Description |
-| -------- | ----------------- |
-| `convId` | The ID of the conversation being created. |
-
-Return values:
-
-The return value of this hook won’t be checked. You can just have the hook return `{}`.
-
-For example, to save the ID of the conversation to a list of recently created conversations on LeanCache after a conversation gets created:
-
-
-
-```js
-AV.Cloud.onIMConversationStarted((request) => {
- redisClient.lpush("recent_conversations", request.params.convId);
- return {};
-});
-```
-
-```python
-@engine.define
-def _conversationStarted(**params):
- redis_client.lpush("recent_conversations", params["convId"])
- return {}
-```
-
-```php
-Cloud::define('_conversationStarted', function($params, $user) {
- $redis->lpush("recent_conversations", $params["convId"]);
- return array();
-});
-```
-
-```java
-@IMHook(type = IMHookType.conversationStarted)
-public static Map onConversationStarted(Map params) throws Exception {
- String convId = (String)params.get("convId");
- jedis.lpush("recent_conversations", params.get("convId"));
- Map result = new HashMap();
- return result;
-}
-```
-
-```cs
-[LCEngineRealtimeHook(LCEngineRealtimeHookType.ConversationStarted)]
-public static object OnConversationStarted(Dictionary parameters) {
- string convId = parameters["convId"] as string;
- Console.WriteLine($"{convId} started");
- return default;
-}
-```
-
-```go
-// Not supported yet
-```
-
-
-
-#### `_conversationAdd`
-
-When a member is joining or being added to a conversation, this hook gets triggered after the signature validation (if enabled) has been completed but before the member actually joins or gets added to the conversation. **Keep in mind that this hook won’t be triggered in the situation when a conversation is being created with other users’ `clientId`s as members.** If a member is joining a conversation, initBy` will be the same as the only element of `members`.
-
-Parameters:
-
-| Parameter | Description |
-| --------- | ----------------------- |
-| `initBy` | The `clientId` of the initiator. |
-| `members` | An array containing the members joining the conversation. |
-| `convId` | The ID of the conversation. |
-
-Return values:
-
-| Parameter | Constraint | Description |
-| -------- | ---- | ---------------------------------------------------------------- |
-| `reject` | Optional | Whether to reject the request. Defaults to `false`. |
-| `code` | Optional | A custom error code (integer) to be returned when `reject` is `true`. |
-| `detail` | Optional | A custom error message (string) to be returned when `reject` is `true`. |
-
-For example, to refuse new members to be added to the conversation created by a specific member:
-
-
-
-```js
-AV.Cloud.onIMConversationAdd((request) => {
- if (request.params.initBy === "Tom") {
- return {
- reject: true,
- code: 9890,
- detail: "This is a private conversation. You cannot add anyone else to it.",
- };
- } else {
- return {};
- }
-});
-```
-
-```python
-@engine.define
-def _conversationAdd(**params):
- if params["initBy"] == "Tom":
- return {
- "reject": True,
- "code": 9890,
- "detail": "This is a private conversation. You cannot add anyone else to it."
- }
- else:
- return {}
-```
-
-```php
-Cloud::define('_conversationAdd', function($params, $user) {
- if ($params["initBy"] === "Tom") {
- return [
- "reject" => true,
- "code" => 9890,
- "detail" => "This is a private conversation. You cannot add anyone else to it.",
- ];
- } else {
- return array();
- }
-});
-```
-
-```java
-@IMHook(type = IMHookType.conversationAdd)
-public static Map onConversationAdd(Map params) {
- Map result = new HashMap();
- if ("Tom".equals(params.get("initBy"))) {
- result.put("reject", true);
- result.put("code", 9890);
- result.put("detail", "This is a private conversation. You cannot add anyone else to it.")
- }
- return result;
-}
-```
-
-```cs
-[LCEngineRealtimeHook(LCEngineRealtimeHookType.ConversationAdd)]
-public static object OnConversationAdd(Dictionary parameters) {
- if ("Tom".Equals(parameters["initBy"])) {
- return new Dictionary {
- { "reject", true },
- { "code", 9890 },
- { "detail", "This is a private conversation. You cannot add anyone else to it." }
- };
- }
- return default;
-}
-```
-
-```go
-// Not supported yet
-```
-
-
-
-#### `_conversationRemove`
-
-When a member is being removed from a conversation, this hook gets triggered after the signature validation (if enabled) has been completed but before the member actually gets removed from the conversation. This hook doesn’t get triggered when a member is leaving a conversation.
-
-Parameters:
-
-| Parameter | Description |
-| --------- | -------------------- |
-| `initBy` | The initiator of the operation. |
-| `members` | An array containing the members to be removed. |
-| `convId` | The ID of the conversation. |
-
-Return values:
-
-| Parameter | Constraint | Description |
-| -------- | ---- | ---------------------------------------------------------------- |
-| `reject` | Optional | Whether to reject the request. Defaults to `false`. |
-| `code` | Optional | A custom error code (integer) to be returned when `reject` is `true`. |
-| `detail` | Optional | A custom error message (string) to be returned when `reject` is `true`. |
-
-For example, to have some staff members in each of the conversations of an application that cannot be removed even by the owner of the conversation:
-
-
-
-```js
-AV.Cloud.onIMConversationRemove(async (request) => {
- const supporters = ["Bast", "Hypnos", "Kthanid"];
- const members = request.params.members;
- for (const member of members) {
- if (supporters.includes(member)) {
- return {
- "reject": true,
- "code": 1928,
- "detail": `You cannot remove the staff member ${member}`,
- };
- }
- }
- return {};
-}
-```
-
-```python
-@engine.define
-def _conversationRemove(**params):
- supporters = ["Bast", "Hypnos", "Kthanid"]
- members = params["members"]
- for member in members:
- if member in supporters:
- return {
- "reject": True,
- "code": 1928,
- "detail": f"You cannot remove the staff member {member}"
- }
- return {}
-```
-
-```php
-Cloud::define('_conversationRemove', function($params, $user) {
- $supporters = array("Bast", "Hypnos", "Kthanid");
- $members = $params["members"];
- foreach ($members as $member) {
- if (in_array($member, $supporters)) {
- return [
- "reject" => true,
- "code" => 1928,
- "detail" => "You cannot remove the staff member $member",
- ];
- }
- }
- return array();
-});
-```
-
-```java
-@IMHook(type = IMHookType.conversationRemove)
-public static Map onConversationRemove(Map params) {
- String[] supporters = {"Bast", "Hypnos", "Kthanid"};
- String[] members = (String[])params.get("members");
- Map result = new HashMap();
- for (String member : members) {
- if (Arrays.asList(supporters).contains(member)) {
- result.put("reject", true);
- result.put("code", 1928);
- result.put("detail", "You cannot remove the staff member " + member);
- }
- }
- return result;
-}
-```
-
-```cs
-[LCEngineRealtimeHook(LCEngineRealtimeHookType.ConversationRemove)]
-public static object OnConversationRemove(Dictionary parameters) {
- List supporters = new List { "Bast", "Hypnos", "Kthanid" };
- List members = parameters["members"] as List;
- foreach (object member in members) {
- if (supporters.Contains(member as string)) {
- return new Dictionary {
- { "reject", true },
- { "code", 1928 },
- { "detail", $"You cannot remove the staff member {member}" }
- };
- }
- }
- return default;
-}
-```
-
-```go
-// Not supported yet
-```
-
-
-
-#### `_conversationAdded`
-
-This hook gets triggered after a user successfully joins a conversation.
-
-Parameters:
-
-| Parameter | Description |
-| --------- | -------------------- |
-| `initBy` | The initiator of the operation. |
-| `convId` | The ID of the conversation. |
-| `members` | An array containing the IDs of the new members. |
-
-Return values:
-
-The return value of this hook won’t be checked.
-
-For example, to send a text message to a staff member if more than 10 members are added to a conversation at once:
-
-
-
-```js
-AV.Cloud.onIMConversationAdded((request) => {
- if (request.params.members.length > 10) {
- AV.Cloud.requestSmsCode({
- mobilePhoneNumber: "+15559463664",
- template: "Group_Notice",
- sign: "sign_example",
- conv_id: request.params.convId,
- }).then(
- function () {
- /* Succeeded */
- },
- function (err) {
- /* Failed */
- }
- );
- }
-});
-```
-
-```python
-@engine.define
-def _conversationAdded(**params):
- if len(params["members"]) > 10:
- cloud.request_sms_code(
- "+15559463664",
- template="Group_Notice", sign: "sign_example",
- params={"conv_id": params["convId"]}
- )
-```
-
-```php
-Cloud::define('_conversationAdded', function($params, $user) {
- if (count($params["members"]) > 10) {
- $options = [
- "template" => "Group_Notice",
- "name" => "sign_example",
- "conv_id" => $params["convId"],
- ];
- SMS::requestSmsCode("+15559463664", $options);
- }
-});
-```
-
-```java
-@IMHook(type = IMHookType.conversationAdded)
-public static void onConversationAdded(Map params) {
- String[] members = (String[])params.get("members");
- if (members.length > 10) {
- LCSMSOption option = new LCSMSOption();
- option.setTemplateName("Group_Notice");
- option.setSignatureName("sign_example");
- Map parameters = new HashMap();
- parameters.put("conv_id", params.get("convId"));
- option.setEnvMap(parameters);
- LCSMS.requestSMSCodeInBackground("+15559463664", option).subscribe(new Observer() {
- @Override
- public void onSubscribe(Disposable disposable) {}
- @Override
- public void onNext(LCNull avNull) {
- Log.d("TAG","Result: Successfully sent text message.");
- }
- @Override
- public void onError(Throwable throwable) {
- Log.d("TAG","Result: Failed to send text message. Reason: " + throwable.getMessage());
- }
- @Override
- public void onComplete() {}
- });
- }
-}
-```
-
-```cs
-[LCEngineRealtimeHook(LCEngineRealtimeHookType.ConversationAdded)]
-public static async Task OnConversationAdded(Dictionary parameters) {
- List members = (parameters["members"] as List)
- .Cast()
- .ToList();
- if (members.Count > 10) {
- Dictionary variables = new Dictionary {
- { "conv_id", request.Params["convId"] }
- };
- try {
- await LCSMSClient.RequestSMSCode("+15559463664", "Group_Notice", "sign_example", variables: variables);
- Console.WriteLine("Successfully sent text message.");
- } catch (Exception e) {
- Console.WriteLine($"Failed to send text message. Reason: {e.Message}");
- }
- }
-}
-```
-
-```go
-// Not supported yet
-```
-
-
-
-#### `_conversationRemoved`
-
-This hook gets triggered after a user successfully leaves a conversation.
-
-Parameters:
-
-| Parameter | Description |
-| --------- | -------------------- |
-| `initBy` | The initiator of the operation. |
-| `convId` | The ID of the conversation. |
-| `members` | An array of the IDs of the users removed from the conversation. |
-
-Return values:
-
-The return value of this hook won’t be checked.
-
-For example, to save the ID of the conversation to a list of recently left conversations on LeanCache after a user leaves a conversation:
-
-
-
-```js
-AV.Cloud.onIMConversationRemoved((request) => {
- const initBy = request.params.initBy;
- const members = request.params.members;
- if (members.length === 1) {
- if (members[0] === initBy) {
- redisClient.lpush(initBy, request.params.convId);
- }
- }
-});
-```
-
-```python
-@engine.define
-def _conversationRemoved(**params):
- init_by = params["initBy"]
- members = params["members"]
- if len(members) == 1:
- if members[0] == init_by:
- redis_client.lpush(init_by, params["convId"])
-```
-
-```php
-Cloud::define('_conversationRemoved', function($params, $user) {
- $initBy = $params['initBy'];
- $members = $params['members'];
- if (count($members) === 1) {
- if (members[0] === $initBy) {
- $redis->lpush($initBy, $params["convId"]);
- }
- }
-});
-```
-
-```java
-@IMHook(type = IMHookType.conversationRemoved)
-public static void onConversationRemoved(Map params) {
- String[] members = (String[])params.get("members");
- String initBy = (String)params.get("initBy");
- if (members.length == 1) {
- if (initBy.equals(members[0])) {
- jedis.lpush(initBy, params.get("convId"));
- }
- }
-}
-```
-
-```cs
-[LCEngineRealtimeHook(LCEngineRealtimeHookType.ConversationRemoved)]
-public static void OnConversationRemoved(Dictionary parameters) {
- List members = (parameters["members"] as List)
- .Cast()
- .ToList();
- string initBy = parameters["initBy"] as string;
- if (members.Count == 1 && members[0].Equals(initBy)) {
- Console.WriteLine($"{parameters["convId"]} removed.");
- }
-}
-```
-
-```go
-// Not supported yet
-```
-
-
-
-#### `_conversationUpdate`
-
-When a conversation’s name, custom attributes, or notification settings are being updated, this hook gets triggered before the update actually takes place.
-
-Parameters:
-
-| Parameter | Description |
-| -------- | ---------------------- |
-| `initBy` | The initiator of the operation. |
-| `convId` | The ID of the conversation. |
-| `mute` | Whether to disable notifications for the current conversation. |
-| `attr` | Attributes to be set to the conversation. |
-
-`mute` and `attr` are mutually exclusive and won’t show up together.
-
-Return values:
-
-| Parameter | Constraint | Description |
-| -------- | ---- | ------------------------------------------------------------------ |
-| `reject` | Optional | Whether to reject the request. Defaults to `false`. |
-| `code` | Optional | A custom error code (integer) to be returned when `reject` is `true`. |
-| `detail` | Optional | A custom error message (string) to be returned when `reject` is `true`. |
-| `attr` | Optional | The updated attributes to be set to the conversation. If omitted, the `attr` in the request will be used. |
-| `mute` | Optional | The updated setting for disabling notifications. If omitted, the `mute` in the request will be used. |
-
-`mute` and `attr` are mutually exclusive and can’t be returned together. The return value should also match what’s in the request. If the request contains `attr`, only the `attr` in the return value will take effect. If the request contains `mute`, the `attr` in the return value will be discarded if it exists.
-
-For example, to prevent the names of conversations from being updated:
-
-
-
-```js
-AV.Cloud.onIMConversationUpdate((request) => {
- if ("attr" in request.params && "name" in request.params.attr) {
- return {
- reject: true,
- code: 1949,
- detail: "The name of the conversation cannot be updated.",
- };
- }
-});
-```
-
-```python
-@engine.define
-def _conversationUpdate(**params):
- if ('attr' in params) and ('name' in params['attr']):
- return {
- "reject": True,
- "code": 1949,
- "detail": "The name of the conversation cannot be updated."
- }
-```
-
-```php
-Cloud::define('_conversationUpdate', function($params, $user) {
- if (array_key_exists('attr', $params) && array_key_exists('name', $params["attr"])) {
- return [
- "reject" => true,
- "code" => 1949,
- "detail" => "The name of the conversation cannot be updated.",
- ];
- }
-});
-```
-
-```java
-@IMHook(type = IMHookType.conversationUpdate)
-public static Map onConversationUpdate(Map params) {
- Map result = new HashMap();
- Map attr = (Map)params.get("attr");
- if (attr != null && attr.containsKey("name")) {
- result.put("reject", true);
- result.put("code", 1949);
- result.put("detail", "The name of the conversation cannot be updated.");
- }
- return result;
-}
-```
-
-```cs
-[LCEngineRealtimeHook(LCEngineRealtimeHookType.ConversationUpdate)]
-public static object OnConversationUpdate(Dictionary parameters) {
- Dictionary attr = parameters["attr"] as Dictionary;
- if (attr != null && attr.ContainsKey("name")) {
- return new Dictionary {
- { "reject", true },
- { "code", 1949 },
- { "detail", "The name of the conversation cannot be updated." }
- };
- }
- return default;
-}
-```
-
-```go
-// Not supported yet
-```
-
-
-
-#### `_clientOnline`
-
-This hook gets triggered when a client logs in successfully.
-
-Keep in mind that this hook only serves as a notification indicating that a user has gone online. If a user quickly goes online and offline (maybe with multiple devices), the sequence the `_clientOnline` and `_clientOffline` hooks get triggered can’t be guaranteed. This means that the `_clientOffline` hook may be triggered for a user before the `_clientOnline` hook gets triggered.
-
-Parameters:
-
-| Parameter | Description |
-| --------- | ----------------------------------------------------------------------------------------------------------- |
-| peerId | The ID of the client logging in. |
-| sourceIP | The IP address of the client logging in. |
-| tag | If left empty or set to "default", other devices with the same `tag` won’t be logged out. Otherwise, other devices with the same `tag` will be logged out. |
-| reconnect | Whether to automatically reconnect for this log-in attempt. If left empty or set to 0, auto-reconnect will be disabled. If set to 1, auto-reconnect will be enabled. |
-
-Return values:
-
-The return value of this hook won’t be checked.
-
-For example, to update the data stored in LeanCache for looking up the online statuses of users:
-
-
-
-```js
-AV.Cloud.onIMClientOnline((request) => {
- // 1 means online
- redisClient.set(request.params.peerId, 1);
-});
-```
-
-```python
-@engine.define
-def _clientOnline(**params):
- # 1 means online
- redis_client.set(params["peerId"], 1)
-```
-
-```php
-Cloud::define('_clientOnline', function($params, $user) {
- // 1 means online
- $redis->set($params["peerId"], 1);
-}
-```
-
-```java
-@IMHook(type = IMHookType.clientOnline)
-public static void onClientOnline(Map params) {
- // 1 means online
- jedis.set(params.get("peerId"), 1);
-}
-```
-
-```cs
-// The code below doesn’t update the data stored in LeanCache but only outputs the online status of the user
-[LCEngineRealtimeHook(LCEngineRealtimeHookType.ClientOnline)]
-public static void OnClientOnline(Dictionary parameters) {
- Console.WriteLine($"{parameters["peerId"]} online.");
-}
-```
-
-```go
-// Not supported yet
-```
-
-
-
-#### `_clientOffline`
-
-This hook gets triggered when a client logs out successfully or loses connection unexpectedly.
-
-Keep in mind that this hook only serves as a notification indicating that a user has gone online. If a user quickly goes online and offline (maybe with multiple devices), the sequence the `_clientOnline` and `_clientOffline` hooks get triggered can’t be guaranteed. This means that the `_clientOffline` hook may be triggered for a user before the `_clientOnline` hook gets triggered.
-
-Parameters:
-
-| Parameter | Description |
-| --------- | --------------------------------------------------------------------------------------------------------------------------------------- |
-| peerId | The ID of the client getting offline. |
-| closeCode | How the client got offline. 1 means the client logged out proactively. 2 means the connection was lost. 3 means the client was logged out due to a duplicate `tag`. 4 means the client was logged out by a request sent to the API. |
-| closeMsg | A message describing how the client got offline. |
-| sourceIP | The IP address of the client closing the session. Will be omitted if the hook is triggered by a loss of connection. |
-| tag | Provided when the session got created. If left empty or set to "default", other devices with the same `tag` won’t be logged out. Otherwise, other devices with the same `tag` will be logged out. |
-| errorCode | The code of the error causing the loss of the connection; optional. |
-| errorMsg | The message of the error causing the loss of the connection; optional. |
-
-Possible errors:
-
-| Error code | Error message | Description |
-| ------ | ------------------- | ------------------------------------ |
-| 4107 | READ_TIMEOUT | The connection timed out due to a lack of new messages or heartbeats for a while. |
-| 4108 | LOGIN_TIMEOUT | The connection timed out due to not logging in for a while. |
-| 4109 | FRAME_TOO_LONG | The WebSocket frame is too long. |
-| 4114 | UNPARSEABLE_RAW_MSG | The message is malformatted and cannot be parsed. |
-| 4200 | INTERNAL_ERROR | There is an internal error in our server. |
-
-Return values:
-
-The return value of this hook won’t be checked.
-
-For example, to update the data stored in LeanCache for looking up the online statuses of users:
-
-
-
-```js
-AV.Cloud.onIMClientOffline((request) => {
- // 0 means offline
- redisClient.set(request.params.peerId, 0);
-});
-```
-
-```python
-@engine.define
-def _clientOffline(**params):
- # 0 means offline
- redis_client.set(params["peerId"], 0)
-```
-
-```php
-Cloud::define('_clientOffline', function($params, $user) {
- // 0 means offline
- $redis->set($params["peerId"], 0);
-}
-```
-
-```java
-@IMHook(type = IMHookType.clientOffline)
-public static void onClientOffline(Map params) {
- // 0 means offline
- jedis.set(params.get("peerId"), 0);
-}
-```
-
-```cs
-// The code below doesn’t update the data stored in LeanCache but only outputs the online status of the user
-[LCEngineRealtimeHook(LCEngineRealtimeHookType.ClientOffline)]
-public static void OnClientOffline(Dictionary parameters) {
- Console.WriteLine($"{parameters["peerId"]} offline");
-}
-```
-
-```go
-// Not supported yet
-```
-
-
-
-## System Conversations
-
-With system conversations, you can easily add functions like auto-reply, official accounts, and service accounts to your application. We have a [demo](https://leancloud.github.io/leanmessage-demo/) that contains a MathBot that can calculate the mathematical expressions sent from users and respond with the results, which is implemented using hooks for system conversations. You can find the server-side program of this bot on [GitHub](https://github.com/leancloud/leanmessage-demo/tree/master/server).
-
-### Creating System Conversations
-
-A system conversation is also a kind of conversation. When a system conversation is created, an entry will be added to the `_Conversation` table with `sys` being `true`. See [Instant Messaging REST API Guide](/sdk/im/guide/rest/) for more information on how to create a system conversation.
-
-### Sending Messages to System Conversations
-
-[Instant Messaging REST API Guide](/sdk/im/guide/rest/) contains detailed instructions on how to send messages to users through system conversations. Besides this, users can send messages to system conversations in the same way they send messages to basic conversations.
-
-System conversations can also be used to send broadcast messages to all the users of your application so you don’t have to manually obtain the IDs of these users before sending messages. All you need to do is to invoke the REST API for sending broadcast messages. A broadcast message has the following traits:
-
-- A broadcast message has to be associated with a conversation. The broadcast message will show up together with other messages in the history of the system conversation.
-- When a user gets online, they will be notified of any broadcast messages sent to them when they are offline.
-- You can set a TTL for a broadcast message so that users won’t be notified of it when getting online if the message has expired. Users will still be able to find the message in the history.
-- When a new user logs in for the first time, they will receive the last broadcast message that’s not expired.
-
-Besides what’s mentioned above, a broadcast message will be treated in the same way as a basic message. See [Instant Messaging REST API Guide](/sdk/im/guide/rest/) for more information on how to send broadcast messages.
-
-### Getting History Messages of System Conversations
-
-To obtain the messages sent to users through system conversations, see [Instant Messaging REST API Guide](/sdk/im/guide/rest/).
-
-To obtain the messages sent from users to system conversations, you can use one of the following ways:
-
-- Look up the `_SysMessage` table. This table gets created the first time a user of your application sends a message to a system conversation. All the messages sent from users to system conversations will be stored in this table.
-- Set up a [Web Hook](#web-hook). You will have to define your [Web Hook](#web-hook) for receiving messages sent from users to system conversations.
-
-### Messages in System Conversations
-
-#### `_SysMessage`
-
-This table contains the messages sent from users to system conversations. It contains the following attributes:
-
-| Attribute | Description |
-| ----------- | ------------------------------ |
-| `ackAt` | The time the message got delivered. |
-| `bin` | Whether this is a binary message. |
-| `conv` | A `Pointer` to the associated system conversation. |
-| `data` | The content of the message. |
-| `from` | The `clientId` of the sender. |
-| `fromIp` | The IP address of the sender. |
-| `msgId` | An internal ID for the message. |
-| `timestamp` | The time the message got created. |
-
-#### Web Hook
-
-To set up a Web Hook for receiving the messages sent from users to system conversations, go to **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Service conversation callback**. The data structure of the messages conforms to the schema of the `_SysMessage` table mentioned earlier.
-
-When users send messages to system conversations, our system will send an HTTP POST request containing data in JSON to the Web Hook you provided. Keep in mind that our system won’t generate a request for each message, but will combine multiple messages into a single request. You’ll notice that the outermost layer of the JSON in a request is an `Array` from the example below.
-
-The timeout for each request will be 5 seconds. If your hook doesn’t respond to a request within the time limit, our system will retry up to 3 times.
-
-The format of a request will look like this:
-
-```json
-[
- {
- "fromIp": "121.238.214.92",
- "conv": {
- "__type": "Pointer",
- "className": "_Conversation",
- "objectId": "55b99ad700b0387b8a3d7bf0"
- },
- "msgId": "nYH9iBSBS_uogCEgvZwE7Q",
- "from": "A",
- "bin": false,
- "data": "Hello sys",
- "createdAt": {
- "__type": "Date",
- "iso": "2015-07-30T14:37:42.584Z"
- },
- "updatedAt": {
- "__type": "Date",
- "iso": "2015-07-30T14:37:42.584Z"
- }
- }
-]
-```
-
-## Previous Chapters
-
-- [Overview](/sdk/im/guide/overview/)
-- Chapter 1: [Basic Conversations and Messages](/sdk/im/guide/beginner/)
-- Chapter 2: [Advanced Messaging Features, Push Notifications, Synchronization, and Multi-Device Sign-on](/sdk/im/guide/intermediate/)
-- Chapter 3: [Security, Chat Rooms, and Temporary Conversations](/sdk/im/guide/senior/)
-
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/im-faq.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/im-faq.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/im-faq.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/im/im-faq.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/server/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/server/_category_.json
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/server/_category_.json
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/im/server/_category_.json
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/server/python.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/server/python.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/server/python.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/im/server/python.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/ui-library/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/ui-library/_category_.json
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/ui-library/_category_.json
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/im/ui-library/_category_.json
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/ui-library/android.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/ui-library/android.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/im/ui-library/android.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/im/ui-library/android.mdx
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/leaderboard/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/leaderboard/_category_.json
deleted file mode 100644
index b53b82043..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/leaderboard/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "排行榜",
- "collapsed": true,
- "position": 8
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/leaderboard/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/leaderboard/features.mdx
deleted file mode 100644
index 0939feee7..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/leaderboard/features.mdx
+++ /dev/null
@@ -1,90 +0,0 @@
----
-title: Leaderboard Introduction
-sidebar_label: Introduction
-sidebar_position: 1
----
-
-:::tip
-
-Setting up leaderboards in your game can promote fun competitions among players and encourage players to interact more with your game.
-
-:::
-
-TapSDK Leaderboard provides the following functions:
-
-- **Automatically calculate rankings:** When the players’ scores are updated, the latest rankings of the players will be automatically re-calculated.
-- **Get the top players:** Get a given number of top players from the current leaderboard.
-- **Get the ranking of the current player:** Get the ranking of the current player no matter if they are on the top of the leaderboard.
-- **Get the players with similar rankings as the current player:** This can be used to find opponents or friends whose levels are close to the current player.
-- **Reset data:** You can determine whether the leaderboard should be reset periodically automatically or manually only. For example, the leaderboard can be automatically reset after a season is over, in which each season lasts for a day, a week, or a month. You may also manually reset the leaderboard while you are testing your game or when there is an error with the data.
-- **Easily update the data:** You can choose among three modes for updating a player’s score on the leaderboard: `better`, `last`, and `sum`. `better` will keep the best score of the player, `last` will keep the latest score, and `sum` will add up all the scores generated by the player.
-
-## Creating a Leaderboard
-
-:::note
-
-We recommend that you create the leaderboards in advance on TapTap Developer Center. The client can then update a leaderboard by providing its name (`statisticName`).
-
-:::
-
-Each leaderboard consists of a name (`statisticName`) and the scores of its members (`statisticValue`). You can specify its order, update strategy, and reset interval as well.
-
-### Name
-
-`statisticName` is the name of the leaderboard, which should be unique within your game. The name cannot be modified after a leaderboard has been created. It can only contain letters, numbers, and underscores, and has to start with a letter.
-
-### Member Type
-
-`memberType` is the type of the members on the leaderboard. You can choose one of the following types:
-
-- User: The value will be `_User`. Each member will correspond to the `objectId` of a user in the built-in account system (the `_User` class). If you plan to update the scores on the leaderboard from the client (for the current players only), this would be your only choice.
-- Object: The value will be the name of a class other than `_User` in the Data Storage service. Each member will correspond to the `objectId` of an object in the class. For example, if you have a class named `Weapon`, you can enter `Weapon` for `memberType` to form a leaderboard for weapons.
-- Entity: The value will be `_Entity`. Each member will be a string provided by you. The string can only contain letters, numbers, and underscores.
-
-Note:
-
-- When retrieving data from the leaderboard, you can specify certain parameters to get more data associated with the users or objects.
-- If you selected “Object” or “Entity” as the type, you will only be able to update the leaderboard **with your Master Key on the server side**.
-- If “Only Master Key is allowed to update the score” is checked on the dashboard (unchecked by default), you will only be able to update the leaderboard with your Master Key on the server side even if you selected “User” as the type. From the perspective of anti-cheating, we recommend that you enable this option.
-
-## Uploading Scores
-
-`statisticValue` contains the scores (in numbers) generated by the players on the client. It could be points, kills, times, etc.
-
-### Order
-
-- `descending`: Rand scores in descending order. In most of the games, the higher score a player achieves, the higher ranking this player gets.
-- `ascending`: Rand scores in ascending order. For example, in some racing games, the less time a player spends to complete a task, the higher ranking this player gets.
-
-### Update Strategy
-
-`updateStrategy` is the strategy for updating players’ scores. Each leaderboard can have one of the following strategies:
-
-- `better`: Keep the **best** score of each player. When the descending order is selected, the largest score will be kept; when the ascending order is selected, the smallest score will be kept.
-- `last`: Keep the **latest** score of each player. This means that a player’s latest score will overwrite their past scores.
-- `sum`: The final score of each player is the sum of all the scores they got. Each time a player gets a new score, the score will be added to the existing score.
-
-## Resetting Data
-
-The leaderboard can be reset at any time. Once reset, the leaderboard will become empty. A use case for resetting is to clear the leaderboard once a season is over. Resetting a leaderboard will remove all the scores from it and the version (`Version`) of the leaderboard will be updated by adding 1. All the new requests from the clients for updating scores will be written to the newer version of the leaderboard.
-
-`versionChangeInterval` is the interval for resetting data. It can be one of the following:
-
-- `day`: Reset at 00:00 every day.
-- `week`: Reset at 00:00 every Monday.
-- `month`: Reset at 00:00 on the 1st of every month.
-- `never`: Do not reset automatically, which means that only manual resets will happen.
-
-## Retrieving History Data
-
-Once the leaderboard is reset, clients can only retrieve the latest and the previous version of the leaderboard with the SDK.
-If you did not check “Keep the previous version” on the dashboard (unchecked by default) before you reset the leaderboard, the client SDK will only be able to retrieve the current version of the leaderboard.
-If the interval for resetting data is set to `never`, there will be an additional restriction: you can only retrieve the previous version within 7 days after you reset the leaderboard. After 7 days, you cannot retrieve the previous version anymore.
-To illustrate:
-
-- With `month` selected as the interval, assuming it’s currently March, the version becomes 3 after the leaderboard has been reset on March 1st. You will be able to retrieve version 2 for February but not version 1 for January.
-- With `never` selected as the interval, assuming the version becomes 3 after you manually reset the leaderboard. You will be able to retrieve version 2 within 7 days. After that, you won’t be able to retrieve version 2 anymore.
-
-Although the previous versions cannot be retrieved by the client SDK, you can still retrieve the archived CSV files containing them with the REST API.
-Each leaderboard can have at most 60 archived versions. After exceeding the limit, **the older versions will be deleted**.
-If you need to keep the previous versions of the leaderboard for a longer time, please download them promptly so you can back them up to your own places.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/leaderboard/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/leaderboard/guide.mdx
deleted file mode 100644
index 725f821fb..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/leaderboard/guide.mdx
+++ /dev/null
@@ -1,2128 +0,0 @@
----
-title: Leaderboard Guide
-sidebar_label: Guide
-sidebar_position: 2
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import { Conditional } from "/src/docComponents/conditional";
-
-:::info
-
-Before continuing, make sure you have read:
-
-- [Leaderboard Introduction](/sdk/leaderboard/features/). It introduces the core concepts and functions of Leaderboard.
-- [TDS Authentication Guide](/sdk/authentication/guide/). There are three types of members: user, object, and entity. Here “user” refers to the users in the built-in account system. Besides, the `currentUser` mentioned later in this page refers to the currently logged-in user.
-
-:::
-
-## Installing and Setting up SDK
-
-Leaderboard comes as a part of the Data Storage SDK. Check the following pages if you haven’t set up the Data Storage SDK.
-
-- [Installing .NET SDK](/sdk/storage/guide/setup-dotnet)
-- [Installing Java SDK](/sdk/storage/guide/setup-java)
-- [Installing Objective-C SDK](/sdk/storage/guide/setup-objc)
-
-## Quick Start
-
-### Creating Leaderboards
-
-There are 3 ways to create a leaderboard:
-
-- Go to the Developer Center’s [dashboard](#dashboard).
-- [Access the REST API](#creating-leaderboards-2) under **trusted environments like the server side**.
-- [Access the management interface of the SDK](#creating-leaderboards-1) under **trusted environments like the server side**.
-
-For example, you can create a leaderboard named `world` with “built-in account system” as the type of members, `descending` as the order, `better` as the update strategy, and “every month” as the reset interval.
-
-![create leaderboard](https://capacity-files.lcfile.com/0UqoMYAOerNgQLfyHiBFxhrULm8kjCPO/io-create_leaderboard.png)
-
-### Submitting Scores
-
-If the player is logged in (accessible with `currentUser`), you can update their score with the following code:
-
-
-
-```cs
-var statistic = new Dictionary();
-statistic["world"] = 20.0;
-await LCLeaderboard.UpdateStatistics(currentUser, statistic);
-```
-
-```java
-Map statistic = new HashMap<>();
-statistic.put("world", 20.0);
-LCLeaderboard.updateStatistic(currentUser, statistic).subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable disposable) {}
-
- @Override
- public void onNext(@NotNull LCStatisticResult jsonObject) {
- // scores saved
- }
-
- @Override
- public void onError(@NotNull Throwable throwable) {
- // handle error
- }
-
- @Override
- public void onComplete() {}
-});
-```
-
-```objc
-NSDictionary *statistic = @{
- @"world" : 20.0,
-};
-[LCLeaderboard updateCurrentUserStatistics:statistic, callback:^(NSArray *statistics, NSError *error) {
- if (statistics) {
- // statistics is the best or latest score after the update
- } else if (error) {
- // Handle error
- }
-}];
-```
-
-
-
-### Getting Results
-
-The code below retrieves the top 10 players from the leaderboard. Since we’ve only submitted one player’s score so far, the leaderboard will only have this one score.
-
-
-
-```cs
-var leaderboard = LCLeaderboard.CreateWithoutData("world");
-var rankings = await leaderboard.GetResults(limit: 10);
-```
-
-```java
-LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("world");
-leaderboard.getResults(0, 10, null, null).subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable disposable) {}
-
- @Override
- public void onNext(@NotNull LCLeaderboardResult leaderboardResult) {
- List rankings = leaderboardResult.getResults();
- // process rankings
- }
-
- @Override
- public void onError(@NotNull Throwable throwable) {
- // handle error
- }
-
- @Override
- public void onComplete() {}
-});
-```
-
-```objc
-LCLeaderboard leaderboard = [[LCLeaderboard alloc] initWithStatisticName:@"world"];
-leaderboard.limit = 10;
-[leaderboard getUserResultsWithOption:nil, callback:^(NSArray *rankings, NSInteger count, NSError *error) {
- // rankings contains the top 10 players’ data
-}];
-```
-
-
-
-We’ve just introduced the most basic usage of Leaderboard. Now let’s look at all the interfaces provided by Leaderboard.
-
-## Managing Scores
-
-### Updating the Current Player’s Scores
-
-Once a player finishes a game, you can use the `updateStatistic` method provided by the client SDK to update this player’s score.
-However, **to effectively prevent cheating from happening, we suggest that you enable “Only Master Key is allowed to update the score” on the dashboard and update scores on the server side only.**
-
-
-
-```cs
-var statistic = new Dictionary {
- { "score", 3458.0 },
- { "kills", 28.0 }
-};
-await LCLeaderboard.UpdateStatistics(currentUser, statistic);
-```
-
-```java
-Map statistic = new HashMap<>();
-statistic.put("score", 3458.0);
-statistic.put("kills", 28.0);
-LCLeaderboard.updateStatistic(currentUser, statistic).subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable disposable) {}
-
- @Override
- public void onNext(@NotNull LCStatisticResult jsonObject) {
- // saved
- }
-
- @Override
- public void onError(@NotNull Throwable throwable) {
- // handle error
- }
-
- @Override
- public void onComplete() {}
-});
-```
-
-```objc
-NSDictionary *statistic = @{
- @"score" : 3458.0,
- @"kills" : 28.0,
-};
-[LCLeaderboard updateCurrentUserStatistics:statistic, callback:^(NSArray *statistics, NSError *error) {
- if (!error) {
- // saved
- }
-}];
-```
-
-
-
-Updating a player’s score requires this player to be logged in. A player can only update their own scores.
-You can update multiple leaderboards at once. The example above updates the scores in both `score` and `kills`.
-
-You cannot update an object or entity’s scores with the client SDK.
-To update other players’, an object’s, or an entity’s scores, you have to use the [REST API](#updating-scores) or the [management interface provided by the SDK](#updating-leaderboard-members-scores).
-
-### Deleting the Current Player’s Scores
-
-A player can delete their own scores:
-
-
-
-```cs
-await LCLeaderboard.DeleteStatistics(currentUser, new List { "world" });
-```
-
-```java
-// Not supported yet
-```
-
-```objc
-[LCLeaderboard deleteCurrentUserStatistics:@[@"world"], callback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- // Score deleted
- } else if (error) {
- // Handle error
- }
-}];
-```
-
-
-
-Same as updating scores, a player can only delete their own scores.
-To delete other players’, an object’s, or an entity’s scores, you have to use the [REST API](#deleting-scores) or the [management interface provided by the SDK](#deleting-leaderboard-members-scores).
-
-### Getting Leaderboard Members’ Scores
-
-A **logged-in player** can retrieve the scores of other players in all leaderboards with `GetStatistics`:
-
-
-
-```cs
-var otherUser = LCObject.CreateWithoutData(TDSUser.CLASS_NAME, "5c76107144d90400536fc88b");
-var statistics = await LCLeaderboard.GetStatistics(otherUser);
-foreach(var statistic in statistics) {
- Debug.Log(statistic.Name);
- Debug.Log(statistic.Value);
-}
-```
-
-```java
-// Retrieve leaderboard members’ scores
-LCUser otherUser = null;
-try {
- otherUser = LCUser.createWithoutData(LCUser.class, "5c76107144d90400536fc88b");
-} catch (LCException e) {
- e.printStackTrace();
-}
-LCLeaderboard.getUserStatistics(otherUser).subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable disposable) {}
-
- @Override
- public void onNext(@NotNull LCStatisticResult lcStatisticResult) {
- List statistics = lcStatisticResult.getResults();
- for (LCStatistic statistic : statistics) {
- Log.d(TAG, statistic.getName());
- Log.d(TAG, String.valueOf(statistic.getValue()));
- }
- }
-
- @Override
- public void onError(@NotNull Throwable throwable) {
- // handle error
- Toast.makeText(MainActivity.this, "Failed: " + throwable.getMessage(), Toast.LENGTH_SHORT).show();
- }
-
- @Override
- public void onComplete() {}
-});
-```
-
-```objc
-NSString *otherUserObjectId = @"5c76107144d90400536fc88b";
-[LCLeaderboard getStatisticsWithUserId:otherUserObjectId, statisticNames:nil, callback:^(NSArray * _Nullable *statistics, NSError _Nullable *error) {
- if (statistics) {
- for (LCLeaderboardStatistic *statistic in statistics) {
- NSLog(@"Leaderboard name: %@", statistic.name);
- NSLog(@"Scores: %f", statistic.value);
- }
- } else if (error) {
- // Handle error
- }
-}];
-```
-
-
-
-In most cases, you may only need to retrieve the scores of a user from specific leaderboards.
-To do so, provide the names of the leaderboards when querying:
-
-
-
-```cs
-var statistics = await LCLeaderboard.GetStatistics(otherUser, new List { "world" });
-```
-
-```java
-LCLeaderboard.getUserStatistics(otherUser, Arrays.asList("world")).subscribe(/** Other logic **/);
-```
-
-```objc
-[LCLeaderboard getStatisticsWithUserId:otherUserObjectId, statisticNames:@[@"world"], callback:^(NSArray * _Nullable statistics, NSError * _Nullable error) {
- // Other logic
-}];
-```
-
-
-
-Similarly, you can retrieve scores from leaderboards with member types being object or entity.
-
-
-<>
-
-For example, if the weapon leaderboard has its member type to be object:
-
-```cs
-var excalibur = LCObject.createWithoutData("Weapon", "582570f38ac247004f39c24b");
-var statistics = await LCLeaderboard.GetStatistics(excalibur, new List { "weapons" });
-```
-
-If the weapon leaderboard has its member type to be entity:
-
-```cs
-var statistics = await LCLeaderboard.GetStatistics("excalibur", new List { "weapons" });
-```
-
->
-<>
-
-For example, if the weapon leaderboard has its member type to be object:
-
-```java
-String excaliburObjectId = "582570f38ac247004f39c24b";
-LCLeaderboard.getMemberStatistics("Weapon", excaliburObjectId,
- Arrays.asList("weapons")).subscribe(/** Other logic **/);
-```
-
-If the weapon leaderboard has its member type to be entity:
-
-```java
-LCLeaderboard.getMemberStatistics(LCLeaderboard.MEMBER_TYPE_ENTITY, "excalibur",
- Arrays.asList("weapons")).subscribe(/** Other logic **/);
-```
-
-The `getUserStatistics` method mentioned earlier:
-
-```java
-LCLeaderboard.getUserStatistics(otherUser, Arrays.asList("weapons")).subscribe(/** Other logic **/);
-```
-
-Is equivalent to:
-
-```java
-LCLeaderboard.getMemberStatistics(LCLeaderboard.LCLeaderboard.MEMBER_TYPE_USER,
- otherUser.getObjectId(),
- Arrays.asList("weapons")).subscribe(/** Other logic **/);
-```
-
->
-<>
-
-For example, if the weapon leaderboard has its member type to be object:
-
-```objc
-NSString *excaliburObjectId = @"582570f38ac247004f39c24b";
-[LCLeaderboard getStatisticsWithObjectId:excaliburObjectId, statisticNames:@[@"weapons"],
- option:nil
- callback:^(NSArray *statistics, NSError *error) {
- // Other logic
-}];
-```
-
-If the weapon leaderboard has its member type to be entity:
-
-```objc
-[LCLeaderboard getStatisticsWithEntity:@"excalibur", statisticNames:@[@"weapons"],
- callback:^(NSArray * _Nullable *statistics, NSError * _Nullable error) {
- // Other logic
-}];
-```
-
->
-
-
-You can also retrieve the scores of a group of members:
-
-
-
-```cs
-var otherUser = LCObject.CreateWithoutData(TDSUser.CLASS_NAME, "5c76107144d90400536fc88b");
-var anotherUser = LCObject.CreateWithoutData(TDSUser.CLASS_NAME, "672a127144a90d00536f3456");
-var statistics = await LCLeaderboard.GetStatistics({otherUser, anotherUser}, new List { "world" });
-
-var oneObject = LCObject.CreateWithoutData("abccb27133a90ddd536ffffa");
-var anotherUser = LCObject.CreateWithoutData("672a1279345777005a2b2444");
-var statistics = await LCLeaderboard.GetStatistics({oneObject, anotherObject}, new List { "weapons" });
-
-var statistics = await LCLeaderboard.GetStatistics({"Sylgr", "Leiptr"}, new List { "rivers" });
-```
-
-```java
-// Not supported yet
-```
-
-```objc
-NSString *otherUserObjectId = @"5c76107144d90400536fc88b";
-NSString *anotherUserObjectId = @"672a127144a90d00536f3456";
-[leaderboard getStatisticsWithUserIds:@[otherUserObjectId, anotherUserObjectId]
- callback:^(NSArray * _Nullable statistics, NSError * _Nullable error) {
-// Other logic
-}];
-
-NSString *oneObjectId = @"abccb27133a90ddd536ffffa";
-NSString *anotherObjectId = @"672a1279345777005a2b2444";
-[leaderboard getStatisticsWithObjectIds:@[oneObjectId, anotherObjectId]
- option:nil
- callback:^(NSArray * _Nullable statistics, NSError * _Nullable error) {
-// Other logic
-}];
-
-[leaderboard getStatisticsWithEntities:@[@"Sylgr", "Leiptr"]
- callback:^(NSArray * _Nullable statistics, NSError * _Nullable error) {
-// Other logic
-}];
-```
-
-
-
-## Getting Leaderboard Results
-
-You can get the result of a leaderboard with the `Leaderboard#getResults` method.
-The most common use case of it is to get the scores of the top players or to get the players with similar rankings as the current player.
-
-Let’s first construct a leaderboard instance:
-
-
-
-<>
-
-```cs
-var leaderboard = LCLeaderboard.CreateWithoutData("world");
-```
-
-`LCLeaderboard.CreateWithoutData` accepts two arguments:
-
-```cs
-public static LCLeaderboard CreateWithoutData(string statisticName, string memberType = LCLeaderboard.USER_MEMBER_TYPE)
-```
-
-- `statisticName` is the name of an existing leaderboard. It’s set to be `world` in the example above.
-- `memberType` is the type of members: `LCLeaderboard.USER_MEMBER_TYPE` for user and `LCLeaderboard.ENTITY_MEMBER_TYPE` for entity. For object, provide the corresponding class name. The example above omitted this argument, which means to default to user.
-
->
-<>
-
-```java
-LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("world");
-```
-
-`LCLeaderboard.createWithoutData` accepts two arguments:
-
-```java
-public static LCLeaderboard createWithoutData(String name, String memberType)
-```
-
-- `name` is the name of an existing leaderboard. It’s set to be `world` in the example above.
-- `memberType` is the type of members: `LCLeaderboard.MEMBER_TYPE_USER` for user and `LCLeaderboard.MEMBER_TYPE_ENTITY` for entity. For object, provide the corresponding class name. Since user is the most common type, `createWithoutData` provides an overload method with a single argument. The example above only passes the name of the leaderboard to the function, which indicates that the type is user.
-
->
-<>
-
-```objc
-LCLeaderboard leaderboard = [[LCLeaderboard alloc] initWithStatisticName:@"world"];
-```
-
->
-
-
-
-After the leaderboard instance is constructed, you can call the corresponding methods on the instance to get the rankings.
-
-### Getting Rankings Within a Scope
-
-To get the top 10 on the leaderboard:
-
-
-
-<>
-
-```cs
-var rankings = await leaderboard.GetResults(limit: 10);
-```
-
-`GetResults` accepts the following arguments for specifying constraints:
-
-| Name | Type | Description |
-| :-----------------: | :--------: | ----------------------------------------------------------------------------------------------------------------------------------------------- |
-| `aroundUser` | `LCUser` | Get the players with similar rankings as a given player. See the next section for more information. |
-| `aroundObject` | `LCObject` | Get the objects with similar rankings as a given object. See the next section for more information. |
-| `aroundEntity` | `string` | Get the entities with similar rankings as a given entity. See the next section for more information. |
-| `limit` | `number` | Limit the number of results. Defaults to 10. |
-| `skip` | `number` | Set the offset. Can be used with `limit` to implement pagination. Defaults to 10. |
-| `selectKeys` | `string[]` | Specify the properties that need to be included with the `user`s from the returned `Ranking`s. Continue reading for more information. |
-| `includeKeys` | `string[]` | Specify the `Pointer` properties that need to be included with the `user`s from the returned `Ranking`s. Continue reading for more information. |
-| `includeStatistics` | `string[]` | Specify the scores in other leaderboards that need to be included in the `Ranking`s. Continue reading for more information. |
-| `version` | `number` | Specify the version of the leaderboard. |
-
-The returned result is an array (`Ranking[]`) with each `Ranking` holding the following properties:
-
-| Name | Type | Description |
-| :------------------: | :---------------: | ------------------------------------------------------- |
-| `Rank` | `int` | The ranking. Starts with 0. |
-| `User` | `LCUser` | The user who got the score (for user leaderboards). |
-| `Object` | `LCObject` | The object who got the score (for object leaderboards). |
-| `Entity` | `string` | The entity who got the score (for entity leaderboards). |
-| `Value` | `double` | The score. |
-| `IncludedStatistics` | `List` | The member’s scores in other leaderboards. |
-
->
-<>
-
-```java
-leaderboard.getResults(0, 10, null, null).subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable disposable) {}
-
- @Override
- public void onNext(@NotNull LCLeaderboardResult leaderboardResult) {
- List rankings = leaderboardResult.getResults();
- }
-
- @Override
- public void onError(@NotNull Throwable throwable) {
- // handle error
- }
-
- @Override
- public void onComplete() {}
-});
-```
-
-`Leaderboard#getResults` accepts the following arguments for specifying constraints:
-
-```java
-Observable getResults(
- int skip, int limit,
- List selectMemberKeys, List includeStatistics)
-```
-
-- `skip` Set the offset. Can be used with `limit` to implement pagination. Defaults to 10.
-- `limit` Limit the number of results. Defaults to 20.
-- `selectMemberKeys` Specify the properties that need to be included with the `user`s from the returned `LCRanking`s. Continue reading for more information.
-- `includeStatistics` Specify the scores in other leaderboards that need to be included in the `LCRanking`s. Continue reading for more information.
-
-By providing a version number, you can retrieve data from a previous version of the leaderboard:
-
-```java
-int previousVersion = currentVersion - 1;
-leaderboard.setVersion(previousVersion);
-```
-
-`LCRanking` provides the following methods for getting members’ information:
-
-```java
-// The ranking; starts with 0
-int getRank()
-// The user who got the score (for user leaderboards)
-LCUser getUser()
-// The object who got the score (for object leaderboards)
-LCObject getObject()
-// The entity who got the score (for entity leaderboards)
-String getEntityId()
-// The score
-double getStatisticValue()
-// The member’s scores in other leaderboards
-List getIncludedStatistics()
-```
-
->
-<>
-
-```objc
-leaderboard.limit = 10;
-[leaderboard getUserResultsWithOption:nil,
- callback:^(NSArray * _Nullable *rankings, NSInteger count, NSError * _Nullable error) {
- // rankings contains the data of the top 10 members in the leaderboard
-}];
-```
-
-Leaderboard accepts the following properties for specifying constraints:
-
-```objc
-/// Set the offset; can be used with `limit` to implement pagination; defaults to 10.
-@property (nonatomic) NSInteger skip;
-/// Limit the number of results; defaults to 20.
-@property (nonatomic) NSInteger limit;
-/// Specify the scores in other leaderboards that need to be included in the `LCLeaderboardRanking`s
-@property (nonatomic, nullable) NSArray *includeStatistics;
-/// Specify the version of the leaderboard; defaults to 0
-@property (nonatomic) NSInteger version;
-```
-
-The first option of `getUserResultsWithOption` is `LCLeaderboardQueryOption`, in which you can specify the properties you want the members of the returned `LCLeaderboardRanking` to include. Continue reading for more information.
-
-The `rankings` in the callback is an `LCLeaderboardRanking` array.
-`LCLeaderboardRanking` contains the following properties:
-
-```objc
-// The name of the leaderboard
-@property (nonatomic, readonly, nullable) NSString *statisticName;
-/// The ranking; starts with 0
-@property (nonatomic, readonly) NSInteger rank;
-/// The score
-@property (nonatomic, readonly) double value;
-/// The member’s scores in other leaderboards
-@property (nonatomic, readonly, nullable) NSArray *includedStatistics;
-/// The user who got the score (for user leaderboards)
-@property (nonatomic, readonly, nullable) LCUser *user;
-/// The object who got the score (for object leaderboards)
-@property (nonatomic, readonly, nullable) LCObject *object;
-/// The entity who got the score (for entity leaderboards)
-@property (nonatomic, readonly, nullable) NSString *entity;
-```
-
-Since the `getUserResultsWithOption` method is called in the previous example, the `user` property is not empty and the `object` and `entity` properties are empty.
-
-For an object or entity leaderboard, the `getObjectResultsWithOption` and `getEntityResultsWithCallback` methods need to be called correspondingly.
-The option for `getObjectResultsWithOption` is the same as that for `getUserResultsWithOption`.
-Since the members of entity leaderboards are strings, `getEntityResultsWithCallback` doesn’t support `LCLeaderboardQueryOption` and its first option is the callback. The callback has the same options as `getUserResultsWithOption` and `getObjectResultsWithOption`:
-
-```objc
-- (void)getEntityResultsWithCallback:(void (^)(NSArray * _Nullable rankings, NSInteger count, NSError * _Nullable error))callback;
-```
-
->
-
-
-
-By default, the `user`s in the results are `LCUser` `Pointer`s.
-To include the usernames or other properties (in the `_User` class) of the users so that they can be displayed like the table below, specify them with `selectKeys`.
-
-| Ranking | Username | Score↓ |
-| :-----: | -------- | :----: |
-| 0 | Genji | 3458 |
-| 1 | Lúcio | 3252 |
-| 2 | D.Va | 3140 |
-
-
-
-```cs
-var rankings = await leaderboard.GetResults(limit: 10,
- selectKeys: new List { "username" });
-```
-
-```java
-List selectKeys = new ArrayList<>();
-selectKeys.add("username");
-leaderboard.getResults(0, 10, selectKeys, null).subscribe(/* Other logic */);
-```
-
-```objc
-leaderboard.limit = 10;
-LCLeaderboardQueryOption *option = [[LCLeaderboardQueryOption alloc] init];
-option.selectKeys = @[@"username"];
-[leaderboard getUserResultsWithOption:option,
- callback:^(NSArray * _Nullable rankings, NSInteger count, NSError * _Nullable error) {
- // Other logic
-}];
-```
-
-
-
-To include the players’ scores in other leaderboards, use `includeStatistics`.
-For example, to include the kills when retrieving the leaderboard for scores:
-
-| Ranking | Username | Score↓ | Kills |
-| :-----: | -------- | :----: | :---: |
-| 0 | Genji | 3458 | 28 |
-| 1 | Lúcio | 3252 | 2 |
-| 2 | D.Va | 3140 | 31 |
-
-
-
-```cs
-var rankings = await leaderboard.GetResults(limit: 10, selectKeys: new List { "username" }
- includeStatistics: new List { "kills" });
-```
-
-```java
-List selectKeys = new ArrayList<>();
-selectKeys.add("username");
-List includeStatistics = new ArrayList<>();
-includeStatistics.add("kills");
-leaderboard.getResults(0, 10, selectKeys, includeStatistics).subscribe(/* Other logic */);
-```
-
-```objc
-leaderboard.limit = 10;
-leaderboard.includeStatistics = @[@"kills"];
-LCLeaderboardQueryOption *option = [[LCLeaderboardQueryOption alloc] init];
-option.selectKeys = @[@"username"];
-[leaderboard getUserResultsWithOption:option,
- callback:^(NSArray * _Nullable rankings, NSInteger count, NSError * _Nullable error) {
- // Other logic
-}];
-```
-
-
-
-If a `Pointer` or file property is included using `selectKeys`, you will only get the `Pointer`s themselves.
-To include the objects referenced by the `Pointer`s, you need to use `includeKeys` as well.
-For example, assuming `club` is a `Club` `Pointer`:
-
-
-
-```cs
-var leaderboard = LCLeaderboard.CreateWithoutData("weapons", "Weapon");
-var rankings = await leaderboard.GetResults(limit: 10,
- selectKeys: new List { "name", "club" },
- includeKeys: new List { "club" });
-```
-
-```java
-// Not supported yet; you can invoke an additional query within the onNext method instead
-```
-
-```objc
-LCLeaderboard leaderboard = [[LCLeaderboard alloc] initWithStatisticName:@"weapons"];
-leaderboard.limit = 10;
-LCLeaderboardQueryOption *option = [[LCLeaderboardQueryOption alloc] init];
-option.selectKeys = @[@"name", @"club"];
-option.includeKeys = @[@"club"];
-[leaderboard getUserResultsWithOption:option,
- callback:^(NSArray * _Nullable rankings, NSInteger count, NSError * _Nullable error) {
- // Other logic
-}];
-```
-
-
-
-Keep in mind that the order of the members with the same score in the returned result is not guaranteed.
-For example, if A, B, and C got 42, 32, and 32, and the leaderboard has a descending order, the result might list the three members in the order of either “A, B, C” or “A, C, B”.
-
-When the score is the same, if you need other factors to set the ranking, you can refer to the following example ** (example is to judge the ranking by the time stamp of the score upload, when the score is the same, the earlier/later the submission, the higher/lower the ranking) ** :
-
-
-Android request example:
-
-```
-public class RankingActivity extends AppCompatActivity{
-
- // ...
-
- /**
- * Upload/update grades
- *
- * */
- private void submitScore() {
-
- double score = 324.45; // Actual score
- long ts = System.currentTimeMillis() / 1000; // time stamp
- double last_score = toEncode( score, ts); // Combine the actual score with the timestamp to generate a new data upload to the server
-
- Map statistic = new HashMap<>();
- statistic.put("word", last_score);
-
- LCLeaderboard.updateStatistic(LCUser.currentUser(), statistic).subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable disposable) {}
-
- @Override
- public void onNext(@NotNull LCStatisticResult jsonObject) {
- Log.e(TAG, "onNext: "+jsonObject.getResults().get(0).toString());
-
- }
-
- @Override
- public void onError(@NotNull Throwable throwable) {
- ToastUtil.showCus(throwable.getMessage(), ToastUtil.Type.ERROR);
- }
-
- @Override
- public void onComplete() {}
- });
-
- }
-
- /**
- * 查询排行榜列表
- * */
- private void searchRankList() {
- // Obtain an instance of a leaderboard
- LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("word");
-
- leaderboard.getResults(0, 10, null, null).subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable disposable) {}
-
- @RequiresApi(api = Build.VERSION_CODES.N)
- @Override
- public void onNext(@NotNull LCLeaderboardResult leaderboardResult) {
- List rankings = leaderboardResult.getResults();
- for(int i=0; i> 32);
-
- /**
- * When the leaderboard is arranged in descending order and the scores are the same, the closer the time and the lower the ranking, use this line of code
- */
- int score = (int) (encryptedNewScore >> 32) + 1;
- long ts = encryptedNewScore & 0xFFFFFFFFL;
- return new int[]{score, (int) ts};
- }
-
-}
-
-```
-
-
-
-### Getting the Players With Similar Rankings as the Current Player
-
-| Ranking | Username | Score↓ |
-| :------: | ---------- | :----: |
-| … | | |
-| 24 | Bastion | 716 |
-| 25 (You) | Widowmaker | 698 |
-| 26 | Hanzo | 23 |
-| … | | |
-
-
-
-<>
-
-To implement something like the table above in your game, provide the current user when calling `GetResults`:
-
-```cs
-var rankings = await leaderboard.GetResults(aroundUser: currentUser, limit: 3, selectKeys: new List { "username" });
-```
-
->
-<>
-
-To implement something like the table above in your game, call the `getAroundResults` method:
-
-```java
-List selectKeys = new ArrayList<>();
-selectKeys.add("username");
-leaderboard.getAroundResults(currentUser.getObjectId(), 0, 3, selectKeys, null).subscribe(/* Other logic */);
-```
-
-The first argument of `getAroundResults` is the ID of the member (`objectId` for user and object; string for entity). Other arguments are the same as those for `getResults`.
-
->
-<>
-
-To implement something like the table above in your game, call the `getUserResultsAroundUser` method:
-
-```objc
-leaderboard.limit = 3;
-[leaderboard getUserResultsAroundUser:currentUser.objectId,
- option:nil,
- callback:^(NSArray * _Nullable rankings, NSInteger count, NSError * _Nullable error) {
- // Other logic
-}];
-```
-
->
-
-
-
-In the example above, the `limit` is set to 3, which means to get two players with similar rankings as the current player together with the current player placed between them. You can set `limit` to 1 to get the current player’s ranking only.
-
-Similarly, you can retrieve objects or entities with similar rankings as a given object or entity.
-For example, to get the weapons with similar rankings as a given weapon from the weapon leaderboard, including their names, attacks, and levels:
-
-
-
-```cs
-var leaderboard = LCLeaderboard.CreateWithoutData("weapons", "Weapon");
-var excalibur = LCObject.createWithoutData("Weapon", "582570f38ac247004f39c24b");
-var rankings = await leaderboard.GetResults(aroundObject: excalibur, limit: 3, selectKeys: new List { "name", "attack", "level" });
-```
-
-```java
-LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("world", "Weapon");
-String excaliburObjectId = "582570f38ac247004f39c24b";
-List selectKeys = new ArrayList<>();
-selectKeys.add("name");
-selectKeys.add("attack");
-selectKeys.add("level");
-leaderboard.getAroundResults(excaliburObjectId, 0, 3, selectKeys, null).subscribe(/* Other logic */);
-```
-
-```objc
-LCLeaderboard leaderboard = [[LCLeaderboard alloc] initWithStatisticName:@"weapons"];
-leaderboard.limit = 3;
-LCLeaderboardQueryOption *option = [[LCLeaderboardQueryOption alloc] init];
-option.selectKeys = @[@"name", @"attack", @"level"];
-NSString *excaliburObjectId = @"582570f38ac247004f39c24b";
-[leaderboard getObjectResultsAroundObject:excaliburObjectId,
- option:option,
- callback:^(NSArray * _Nullable rankings, NSInteger count, NSError * _Nullable error) {
- // Other logic
-}];
-```
-
-
-
-The example above assumes that the weapon leaderboard is an object leaderboard and the class for storing weapons is named `Weapon`.
-If the weapon leaderboard has entity as its type, and you only need to retrieve the names of the weapons, the following code would apply:
-
-
-
-```cs
-var leaderboard = LCLeaderboard.CreateWithoutData("weapons", LCLeaderboard.ENTITY_MEMBER_TYPE);
-var rankings = await leaderboard.GetResults(aroundEntity: "excalibur", limit: 3);
-```
-
-```java
-LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("world", LCLeaderboard.ENTITY_MEMBER_TYPE);
-leaderboard.getAroundResults("excalibur", 0, 3, null, null).subscribe(/* Other logic */);
-```
-
-```objc
-LCLeaderboard leaderboard = [[LCLeaderboard alloc] initWithStatisticName:@"weapons"];
-leaderboard.limit = 3;
-[leaderboard getEntityResultsAroundEntity:@"excalibur",
- callback:^(NSArray^(NSArray * _Nullable rankings, NSInteger count, NSError * _Nullable error) {
- // Other logic
-}];
-```
-
-
-
-## Dashboard
-
-On **Game Services > Cloud Services > Leaderboard**, you can:
-
-- Create, reset, edit, and delete leaderboards.
-- View the current versions of the leaderboards, delete scores, and download the archives of the earlier versions of the leaderboards.
-- Configure whether the client can retrieve the previous version of each leaderboard, and whether scores can be updated with the Master Key only.
-
-## SDK Management Interface
-
-Besides using the dashboard, you can also manage leaderboards with the management interfaces provided by the C# SDK and the Java SDK. Those interfaces can be used **in trusted environments including the server side**.
-Another way to access the management interface is to use the REST API.
-
-Let’s first take a look at the management interfaces provided by the SDKs.
-
-:::caution
-
-To use the management interface, the SDK needs to be initialized with the `masterKey`. Therefore, **you should only use the management interface in trusted environments like the server side, and not the client side.**
-
-:::
-
-
-
-```cs
-LCApplication.Initialize({{appid}}, {{appkey}}, "https://xxx.example.com", {{masterkey}});
-LCApplication.UseMasterKey = true;
-```
-
-```java
-LeanCloud.setMasterKey({{masterkey}});
-```
-
-```objc
-// Not supported yet
-```
-
-
-
-### Creating Leaderboards
-
-
-
-<>
-
-```cs
-var leaderboard = await LCLeaderboard.CreateLeaderboard("time", order: LCLeaderboardOrder.ASCENDING);
-```
-
-Below are the available options and their default values:
-
-```cs
-public static async Task CreateLeaderboard(string statisticName,
- LCLeaderboardOrder order = LCLeaderboardOrder.Descending,
- LCLeaderboardUpdateStrategy updateStrategy = LCLeaderboardUpdateStrategy.Better,
- LCLeaderboardVersionChangeInterval versionChangeInterval = LCLeaderboardVersionChangeInterval.Week,
- string memberType = LCLeaderboard.USER_MEMBER_TYPE)
-```
-
-- `statisticName` The name of the leaderboard.
-- `order` The order. Can be `LCLeaderboardOrder.Descending` or `LCLeaderboardOrder.Ascending`.
-- `updateStrategy` The strategy for updating scores. Can be `LCLeaderboardUpdateStrategy.Better`, `LCLeaderboardUpdateStrategy.Last`, or `LCLeaderboardUpdateStrategy.Sum`.
-- `versionChangeInterval` The interval for resetting the leaderboard. Can be `LCLeaderboardVersionChangeInterval.Never`, `LCLeaderboardVersionChangeInterval.Day`, `LCLeaderboardVersionChangeInterval.Week`, or `LCLeaderboardVersionChangeInterval.Month`.
-- `memberType` The type of the members. Use `LCLeaderboard.USER_MEMBER_TYPE` for user and `LCLeaderboard.ENTITY_MEMBER_TYPE` for entity. For object, use the name of the class.
-
->
-<>
-
-```java
-LCLeaderboard.createWithMemberType(LCLeaderboard.MEMBER_TYPE_USER, "time",
- LCLeaderboard.LCLeaderboardOrder.Ascending,
- LCLeaderboard.LCLeaderboardUpdateStrategy.Last,
- LCLeaderboard.LCLeaderboardVersionChangeInterval.Day).subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable disposable) {}
-
- @Override
- public void onNext(@NotNull final LCLeaderboard lcLeaderboard) {
- System.out.println("leaderboard created");
- }
-
- @Override
- public void onError(@NotNull Throwable throwable) {
- System.out.println("failed to create leaderboard. Cause " + throwable);
- }
-
- @Override
- public void onComplete() {}
-});
-```
-
-Below are the available options:
-
-```java
-public static Observable createWithMemberType(String memberType, String name,
- LCLeaderboardOrder order,
- LCLeaderboardUpdateStrategy updateStrategy,
- LCLeaderboardVersionChangeInterval versionChangeInterval)
-```
-
-- `memberType` The type of the members. Use `LCLeaderboard.MEMBER_TYPE_USER` for user and `LCLeaderboard.MEMBER_TYPE_ENTITY` for entity. For object, use the name of the class.
-- `name` The name of the leaderboard.
-- `order` The order. Can be `LCLeaderboard.LCLeaderboardOrder.Descending` (default) or `LCLeaderboard.LCLeaderboardOrder.Ascending`.
-- `updateStrategy` The strategy for updating scores. Can be `LCLeaderboard.LCLeaderboardUpdateStrategy.Better` (default), `LCLeaderboard.LCLeaderboardUpdateStrategy.Last`, or `LCLeaderboard.LCLeaderboardUpdateStrategy.Sum`.
-- `versionChangeInterval` The interval for resetting the leaderboard. Can be `LCLeaderboard.LCLeaderboardVersionChangeInterval.Never`, `LCLeaderboard.LCLeaderboardVersionChangeInterval.Day`, `LCLeaderboard.LCLeaderboardVersionChangeInterval.Week` (default), or `LCLeaderboard.LCLeaderboardVersionChangeInterval.Month`.
-
-“Default” indicates the value used when `null` is provided.
-
->
-
-<>
-
-Not supported yet.
-
->
-
-
-
-### Manually Resetting the Leaderboard
-
-
-
-```cs
-var leaderboard = LCLeaderboard.CreateWithoutData("score");
-await leaderboard.Reset();
-```
-
-```java
-LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("score");
-leaderboard.reset().subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable disposable) {
- }
-
- @Override
- public void onNext(@NotNull Boolean aBoolean) {
- if (aBoolean) { // aBoolean should always be true
- System.out.println("leaderboard reset");
- }
- }
-
- @override
- public void onerror(@notnull throwable throwable) {
- system.out.println("Failed to reset leaderboard. Cause " + throwable);
- }
-
- @override
- public void oncomplete() {}
-});
-```
-
-```objc
-// Not supported yet
-```
-
-
-
-### Retrieving Leaderboard Properties
-
-Use the following interface to get the properties of a leaderboard, including its reset interval, version, and update strategy.
-
-
-
-```cs
-var leaderboardData = await LCLeaderboard.GetLeaderboard("world");
-```
-
-```java
-LCLeaderboard leaderboard = LCLeaderboard.fetchByName("world").blockingFirst();
-```
-
-```objc
-// Not supported yet
-```
-
-
-
-### Updating Leaderboard Properties
-
-Once a leaderboard is created, only its reset interval and update strategy can be updated. Other properties cannot be modified.
-
-
-
-```cs
-var leaderboard = LCLeaderboard.CreateWithoutData("equip");
-await leaderboard.UpdateVersionChangeInterval(LCLeaderboardVersionChangeInterval.Week);
-await leaderboard.UpdateUpdateStrategy(LCLeaderboardUpdateStrategy.Last);
-```
-
-```java
-LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("equip");
-leaderboard.updateVersionChangeInterval(LCLeaderboard.LCLeaderboardVersionChangeInterval.Week)
- .subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable disposable) {
- }
-
- @Override
- public void onNext(@NotNull Boolean aBoolean) {
- if (aBoolean) { // aBoolean should always be true
- System.out.println("version update interval updated");
- }
- }
-
- @override
- public void onerror(@notnull throwable throwable) {
- system.out.println("Failed to change version update interval. Cause: " + throwable);
- }
-
- @override
- public void oncomplete() {}
-});
-leaderboard.updateUpdateStrategy(LCLeaderboard.LCLeaderboardUpdateStrategy.Last)
- .subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable disposable) {
- }
-
- @Override
- public void onNext(@NotNull Boolean aBoolean) {
- if (aBoolean) { // aBoolean should always be true
- System.out.println("update strategy updated");
- }
- }
-
- @override
- public void onerror(@notnull throwable throwable) {
- system.out.println("Failed to change update strategy. Cause: " + throwable);
- }
-
- @override
- public void oncomplete() {}
-});
-```
-
-```objc
-// Not supported yet
-```
-
-
-
-### Deleting Leaderboards
-
-
-
-```cs
-var leaderboard = lcleaderboard.createwithoutdata("equip");
-await leaderboard.destroy();
-```
-
-```java
-LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("equip");
-leaderboard.destroy().subscribe(new Observer() {
- @Override
- public void onSubscribe(@NotNull Disposable disposable) {
- }
-
- @Override
- public void onNext(@NotNull Boolean aBoolean) {
- if (aBoolean) { // aBoolean should always be true
- System.out.println("leaderboard deleted");
- }
- }
-
- @override
- public void onerror(@notnull throwable throwable) {
- system.out.println("Failed to delete leaderboard. Cause " + throwable);
- }
-
- @override
- public void oncomplete() {}
-});
-```
-
-```objc
-// Not supported yet
-```
-
-
-
-**Deleting a leaderboard will delete all the data within it, including the current version and the archives of the past versions.**
-
-### Updating Leaderboard Members’ Scores
-
-You can use the `overwrite` option to bypass the update strategy and force update a member’s score:
-
-
-
-```cs
-var statistic = new Dictionary {
- { "score", 0.0 }
-};
-await LCLeaderboard.UpdateStatistics(user, statistic, overwrite: true);
-```
-
-```java
-Map statistic = new HashMap<>();
-statistic.put("world", 0.0);
-LCLeaderboard.updateStatistic(currentUser, statistic, true).subscribe(/** Other logic **/);
-```
-
-```objc
-// Not supported yet
-```
-
-
-
-Object and entity leaderboards can only be updated on the server side with the Master Key. The update strategy of the leaderboard will still be followed, though:
-
-
-
-```cs
-var excalibur = LCObject.createWithoutData("Weapon", "582570f38ac247004f39c24b");
-await LCLeaderboard.UpdateStatistics(excalibur, statistic);
-```
-
-```java
-// Not supported yet; consider using the REST API
-```
-
-```objc
-Not supported yet.
-```
-
-
-
-To force update the data, set `overwrite` to `true`:
-
-
-
-```cs
-await LCLeaderboard.UpdateStatistics("Vimur", statistic, overwrite: true);
-```
-
-```java
-// Not supported yet; consider using the REST API
-```
-
-```objc
-// Not supported yet
-```
-
-
-
-### Deleting Leaderboard Members’ Scores
-
-Use the Master Key on the server side to delete the score of any user, object, or entity:
-
-
-
-```cs
-var otherUser = LCObject.CreateWithoutData(TDSUser.CLASS_NAME, "5c76107144d90400536fc88b");
-await LCLeaderboard.DeleteStatistics(otherUser, new List { "world" });
-
-var excalibur = LCObject.createWithoutData("Weapon", "582570f38ac247004f39c24b");
-await LCLeaderboard.DeleteStatistics(excalibur, new List { "weapons" });
-
-await LCLeaderboard.DeleteStatistics("Vimur", new List { "rivers" });
-```
-
-```java
-// Not supported yet; consider using the REST API
-```
-
-```objc
-// Not supported yet
-```
-
-
-
-## REST API
-
-Now we will introduce the Leaderboard-related REST API interfaces.
-You can write your own programs or scripts to access these interfaces to perform administrative operations on the server side.
-
-### Request Format
-
-For POST and PUT requests, the body of the request must be in JSON, and the `Content-Type` of the HTTP Header should be `application/json`.
-
-Requests are authenticated by the following key-value pairs in the HTTP Header:
-
-| Key | Value | Meaning | Source |
-| ---------- | ------------ | ------------------------------------------------- | ------------------------------------ |
-| `X-LC-Id` | `{{appid}}` | The `App Id` (`Client Id`) of the current app | Can be found on the Developer Center |
-| `X-LC-Key` | `{{appkey}}` | The `App Key` (`Client Token`) of the current app | Can be found on the Developer Center |
-
-To access the management interface, the `Master Key` is required: `X-LC-Key: {{masterkey}},master`.
-`Master Key` is also named `Server Secret`, which can be found on the Developer Center as well.
-
-See [Credentials](/sdk/storage/guide/setup-dotnet#credentials) for more information.
-
-### Base URL
-
-The Base URL for the REST API (`{{host}}` in curl examples) is the app’s custom API domain, which can be added and viewed on the Developer Center. See [Domain](/sdk/storage/guide/setup-dotnet#domain) for more information.
-
-### Managing Leaderboards
-
-#### Creating Leaderboards
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"statisticName": "world", "memberType": "_User", "order": "descending", "updateStrategy": "better", "versionChangeInterval": "month"}' \
- https://{{host}}/1.1/leaderboard/leaderboards
-```
-
-| Parameter | Required | Description |
-| ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
-| `statisticName` | Required | The name of the leaderboard. Cannot be edited once the leaderboard is created. |
-| `memberType` | Required | The type of the members. Cannot be edited once the leaderboard is created. Can be `_Entity`, `_User`, or the name of an existing class. |
-| `order` | Optional | The strategy for ordering. Cannot be edited once the leaderboard is created. Can be `ascending` or `descending`. Defaults to `descending`. |
-| `updateStrategy` | Optional | Can be `better`, `last`, or `sum`. Defaults to `better`. |
-| `versionChangeInterval` | Optional | Can be `day`, `week`, `month`, or `never`. Defaults to `week`. |
-
-The response body will be a JSON object containing all the parameters provided when creating the leaderboard, as well as the following fields:
-
-- `version` The version of the leaderboard.
-- `expiredAt` The time the leaderboard will be reset for the next time.
-- `activatedAt` The time the current version started.
-
-```json
-{
- "objectId": "5b62c15a9f54540062427acc",
- "statisticName": "world",
- "memberType": "_User",
- "versionChangeInterval": "month",
- "order": "descending",
- "updateStrategy": "better",
- "version": 0,
- "createdAt": "2018-08-02T08:31:22.294Z",
- "updatedAt": "2018-08-02T08:31:22.294Z",
- "expiredAt": {
- "__type": "Date",
- "iso": "2018-08-31T16:00:00.000Z"
- },
- "activatedAt": {
- "__type": "Date",
- "iso": "2018-08-02T08:31:22.290Z"
- }
-}
-```
-
-#### Retrieving Leaderboard Properties
-
-The following interface allows you to retrieve the properties of a leaderboard, including its update strategy and version number.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.1/leaderboard/leaderboards/
-```
-
-The returned JSON object contains all the information related to the leaderboard:
-
-```json
-{
- "objectId": "5b0b97cf06f4fd0abc0abe35",
- "statisticName": "world",
- "memberType": "_User",
- "order": "descending",
- "updateStrategy": "better",
- "version": 5,
- "versionChangeInterval": "day",
- "expiredAt": { "__type": "Date", "iso": "2018-05-02T16:00:00.000Z" },
- "activatedAt": { "__type": "Date", "iso": "2018-05-01T16:00:00.000Z" },
- "createdAt": "2018-04-28T05:46:58.579Z",
- "updatedAt": "2018-05-01T01:00:00.000Z"
-}
-```
-
-#### Updating Leaderboard Properties
-
-The following interface allows you to update the `updateStrategy` and `versionChangeInterval` of a leaderboard. Properties other than these cannot be updated. You can update only one of the two properties. For example, to update `versionChangeInterval` only:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"versionChangeInterval": "day"}' \
- https://{{host}}/1.1/leaderboard/leaderboards/
-```
-
-The returned JSON object contains all the updated fields as well as an `updatedAt` field.
-
-```json
-{
- "objectId": "5b0b97cf06f4fd0abc0abe35",
- "versionChangeInterval": "day",
- "updatedAt": "2018-05-01T08:01:00.000Z"
-}
-```
-
-#### Resetting Leaderboards
-
-The following interface allows you to reset a leaderboard regardless of its reset strategy. Once you reset a leaderboard, the current version of it will be cleared and the cleared data will be archived as a CSV file for you to download. The `version` of the leaderboard will automatically increment by 1.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.1/leaderboard/leaderboards//incrementVersion
-```
-
-The returned JSON object will contain the new version number, the time the leaderboard will be reset for the next time (`expiredAt`), and the time the current version started (`activatedAt`):
-
-```json
-{
- "objectId": "5b0b97cf06f4fd0abc0abe35",
- "version": 7,
- "expiredAt": { "__type": "Date", "iso": "2018-06-03T16:00:00.000Z" },
- "activatedAt": { "__type": "Date", "iso": "2018-05-28T06:02:56.169Z" },
- "updatedAt": "2018-05-28T06:02:56.185Z"
-}
-```
-
-#### Retrieving Archives
-
-Since each leaderboard can hold at most 60 archive files, we recommend that you retrieve the archived files regularly and back them up in your own places with the following interface.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -G \
- --data-urlencode 'limit=10' \
- https://{{host}}/1.1/leaderboard/leaderboards//archives
-```
-
-The returned objects will be in decreasing order by `createdAt`. For each object, it has the file name (`file_key`), the URL for downloading (`url`), and a `status` property being one of the following statuses:
-
-- `scheduled`: The archiving process is queued. This usually won’t last very long.
-- `inProgress`: Archiving in progress.
-- `failed`: Failed to archive. Please reach out to our technical support.
-- `completed`: Successfully archived.
-
-```json
-{
- "results": [
- {
- "objectId": "5b0b9da506f4fd0abc0abe6e",
- "statisticName": "wins",
- "version": 9,
- "status": "completed",
- "url": "https://lc-paas-files.cn-n1.lcfile.com/yK5s6YJztAwEYiWs.csv",
- "file_key": "yK5s6YJztAwEYiWs.csv",
- "activatedAt": { "__type": "Date", "iso": "2018-05-28T06:11:49.572Z" },
- "deactivatedAt": { "__type": "Date", "iso": "2018-05-30T06:11:49.951Z" },
- "createdAt": "2018-05-01T16:00.00.000Z",
- "updatedAt": "2018-05-28T06:11:50.129Z"
- }
- ]
-}
-```
-
-#### Deleting Leaderboards
-
-**This will delete everything within the leaderboard**, including the current version and all the archives. You won’t be able to undo this operation.
-
-Provide the `statisticName` of the leaderboard to delete it.
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.1/leaderboard/leaderboards/
-```
-
-Once done, an empty JSON object will be returned:
-
-```json
-{}
-```
-
-### Managing Scores
-
-#### Updating Scores
-
-Use the Master Key to update any score while still following the `updateStrategy`.
-
-Provide the corresponding user’s `objectId` when you update their score:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '[{"statisticName": "wins", "statisticValue": 5}, {"statisticName": "world", "statisticValue": 91}]' \
- https://{{host}}/1.1/leaderboard/users//statistics
-```
-
-The returned data will be the current score:
-
-```sh
-{
- "results": [
- {
- "statisticName": "wins",
- "version": 0,
- "statisticValue": 5
- },
- {
- "statisticName": "world",
- "version": 2,
- "statisticValue": 91
- }
- ]
-}
-```
-
-Similarly, you can provide the `objectId` of an object when updating its score:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '[{"statisticName": "wins", "statisticValue": 5}, {"statisticName": "weapons","statisticValue": 91}]' \
- https://{{host}}/1.1/leaderboard/objects//statistics
-```
-
-For entity, provide the string for it:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '[{"statisticName": "wins", "statisticValue": 5}, {"statisticName": "cities","statisticValue": 91}]' \
- https://{{host}}/1.1/leaderboard/entities//statistics
-```
-
-The current user can update their own score, though this won’t require the `Master Key` for the management interface. However, the `sessionToken` of the current user needs to be provided (the SDK has already encapsulated this interface):
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: " \
- -H "Content-Type: application/json" \
- -d '[{"statisticName": "wins", "statisticValue": 5}, {"statisticName": "world", "statisticValue": 91}]' \
- https://{{host}}/1.1/leaderboard/users/self/statistics
-```
-
-#### Force Updating Scores
-
-Add `overwrite=1` to ignore the `better` and `sum` update strategies and use `last` instead.
-For example, if cheating is detected on a user, you can force update the user’s score with this interface.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '[{"statisticName": "wins", "statisticValue": 10}]' \
- https://{{host}}/1.1/leaderboard/users//statistics?overwrite=1
-```
-
-The returned data is the current score:
-
-```json
-{ "results": [{ "statisticName": "wins", "version": 0, "statisticValue": 10 }] }
-```
-
-`overwrite=1` can be used for object scores and entity scores as well.
-
-#### Deleting Scores
-
-Use this interface to remove the score and ranking of a user from the leaderboard. Note that only the score on the current version of the leaderboard can be removed.
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- https://{{host}}/1.1/leaderboard/users//statistics?statistics=wins,world
-```
-
-Once done, an empty object will be returned:
-
-```
-{}
-```
-
-Similarly, you can delete the score of an object:
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- --data-urlencode 'statistics=weapons,equipments' \
- https://{{host}}/1.1/leaderboard/objects//statistics
-```
-
-And the score of an entity:
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- --data-urlencode 'statistics=cities' \
- https://{{host}}/1.1/leaderboard/entities//statistics
-```
-
-The current user can delete their own score, though this won’t require the `Master Key` for the management interface. However, the `sessionToken` of the current user needs to be provided (the SDK has already encapsulated this interface):
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: " \
- -H "Content-Type: application/json" \
- https://{{host}}/1.1/leaderboard/users/self/statistics?statistics=wins,world
-```
-
-### Retrieving Scores
-
-The REST API interfaces for retrieving scores are not management interfaces and the `Master Key` is not required:
-
-#### Retrieving a Single Score
-
-Retrieve a score by providing the `objectId` of a user.
-You can specify multiple leaderboards within the `statistics` property (separated by `,`) to get the scores of the user in all the given leaderboards. If this option is not provided, the user’s scores in all leaderboards will be returned.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- --data-urlencode 'statistics=wins,world' \
- https://{{host}}/1.1/leaderboard/users//statistics
-```
-
-Response:
-
-```json
-{
- "results": [
- {
- "statisticName": "wins",
- "statisticValue": 5,
- "version": 0,
- "user": {
- "__type": "Pointer",
- "className": "_User",
- "objectId": "60d950629be318a249000001"
- }
- },
- {
- "statisticName": "world",
- "statisticValue": 91,
- "version": 0,
- "user": {...}
- }
- ]
-}
-```
-
-Similarly, you can get an object’s scores by providing its `objectId`:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- --data-urlencode 'statistics=wins,world' \
- https://{{host}}/1.1/leaderboard/objects//statistics
-```
-
-Response:
-
-```json
-{
- "results": [
- {
- "statisticName": "wins",
- "statisticValue": 5,
- "version": 0,
- "object": {
- "__type": "Pointer",
- "className": "Weapon",
- "objectId": "60d1af149be3180684000002"
- }
- },
- {
- "statisticName": "world",
- "statisticValue": 91,
- "version": 0,
- "object": {
- "__type": "Pointer",
- "className": "Weapon",
- "objectId": "60d1af149be3180684000002"
- }
- }
- ]
-}
-```
-
-To get an entity’s scores, provide the string for the entity:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- --data-urlencode 'statistics=wins,world' \
- https://{{host}}/1.1/leaderboard/entities//statistics
-```
-
-Response:
-
-```json
-{
- "results": [
- {
- "statisticName": "wins",
- "statisticValue": 5,
- "version": 0,
- "entity": "1a2b3c4d"
- },
- {
- "statisticName": "world",
- "statisticValue": 91,
- "version": 0,
- "entity": "1a2b3c4d"
- }
- ]
-}
-```
-
-#### Retrieving a Group of Scores
-
-With this interface, you can get the scores of no more than 200 users at once. To use this interface, provide an array of `objectId`s of the users in the body.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '["60d950629be318a249000001", "60d950629be318a249000000"]'
- https://{{host}}/1.1/leaderboard/users/statistics/
-```
-
-The response is similar to that of [retrieving a single score](#retrieving-a-single-score):
-
-```json
-{
- "results": [
- {
- "statisticName": "wins",
- "statisticValue": 1,
- "version": 0,
- "user": {
- "__type": "Pointer",
- "className": "_User",
- "objectId": "60d950629be318a249000001"
- }
- },
- {
- "statisticName": "wins",
- "statisticValue": 2,
- "version": 0,
- "user": {
- "__type": "Pointer",
- "className": "_User",
- "objectId": "60d950629be318a249000000"
- }
- }
- ]
-}
-```
-
-Similarly, provide a list of `objectId`s of objects (no more than 200) to get these objects’ scores:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '["60d950629be318a249000001", "60d950629be318a249000000"]'
- https://{{host}}/1.1/leaderboard/objects/statistics/
-```
-
-Provide a list of strings of entities (no more than 200) to get these entities’ scores:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '["Vimur", "Fimbulthul"]'
- https://{{host}}/1.1/leaderboard/entities/statistics/
-```
-
-### Queries on Leaderboards
-
-#### Retrieving Scores Within a Scope
-
-Use this interface to retrieve the top players.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'startPosition=0' \
- --data-urlencode 'maxResultsCount=20' \
- --data-urlencode 'selectKeys=username,club' \
- --data-urlencode 'includeKeys=club' \
- --data-urlencode 'includeStatistics=wins' \
- https://{{host}}/1.1/leaderboard/leaderboards/user//ranks
-```
-
-| Parameter | Required | Description |
-| ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| startPosition | Optional | The offset of the query. Defaults to 0. |
-| maxResultsCount | Optional | The maximum number of results. Defaults to 20. |
-| selectKeys | Optional | Return the other fields of the user in the `_User` class. You can provide multiple fields separated by `,`. For security reasons, the `email` and `mobilePhoneNumber` fields will not be returned if you are not using the `masterKey`. |
-| includeKeys | Optional | Return the referenced objects of the other fields of the user in the `_User` class. You can provide multiple fields separated by `,`. For security reasons, the `email` and `mobilePhoneNumber` fields will not be returned if you are not using the `masterKey`. |
-| includeStatistics | Optional | Return the user’s scores in other leaderboards. If a non-existant leaderboard is provided, an error will be returned. |
-| version | Optional | Return the results from a specific version. By default, the results from the current version will be returned. |
-| count | Optional | If set to 1, the number of members in the leaderboard will be returned. Defaults to 0. |
-
-The response will be a JSON object:
-
-```json
-{
- "results": [
- {
- "statisticName": "world",
- "statisticValue": 91,
- "rank": 0,
- "user": {
- "__type": "Pointer",
- "className": "_User",
- "updatedAt": "2021-07-21T03:08:10.487Z",
- "username": "zw1stza3fy701rvgxqwiikex7",
- "createdAt": "2020-09-04T04:23:04.795Z",
- "club": {
- "objectId": "60f78f98d9f1465d3b1da12d",
- "name": "board games",
- "updatedAt": "2021-07-21T03:08:08.692Z",
- "createdAt": "2021-07-21T03:08:08.692Z",
- },
- "objectId": "5f51c1287628f2468aa696e6"
- }
- },
- {...}
- ],
- "count": 500
-}
-```
-
-Querying on object leaderboards shares a similar interface. The only difference is that `user` will be replaced by `object`:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'startPosition=0' \
- --data-urlencode 'maxResultsCount=2' \
- --data-urlencode 'selectKeys=name,image' \
- --data-urlencode 'includeKeys=image' \
- --data-urlencode 'count=1' \
- https://{{host}}/1.1/leaderboard/leaderboards/object//ranks
-```
-
-Response:
-
-```json
-{
- "results": [
- {
- "statisticName": "wins",
- "statisticValue": 4,
- "rank": 0,
- "object": {
- "__type": "Pointer",
- "className": "Weapon",
- "name": "sword",
- "image": {
- "bucket": "test_files",
- "provider": "leancloud",
- "name": "sword.jpg",
- "url": "https://example.com/sword.jpg",
- "objectId": "60d2f3a39be3183377000002",
- "__type": "File"
- },
- "objectId": "60d2f22f9be318328b000007"
- }
- },
- {
- "statisticName": "wins",
- "statisticValue": 3,
- "rank": 1,
- "object": {...}
- }
- ],
- "count": 500
-}
-```
-
-Change `user` to `entity` to query on entity leaderboards:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'startPosition=0' \
- --data-urlencode 'maxResultsCount=2' \
- --data-urlencode 'count=1' \
- https://{{host}}/1.1/leaderboard/leaderboards/entity//ranks
-```
-
-Response:
-
-```json
-{
- "results": [
- {
- "statisticName": "wins",
- "statisticValue": 4,
- "rank": 0,
- "entity": "1234567890"
- },
- {
- "statisticName": "wins",
- "statisticValue": 3,
- "rank": 1,
- "entity": "2345678901"
- }
- ],
- "count": 500
-}
-```
-
-#### Retrieving Members With Similar Rankings as the Given Member
-
-Add the corresponding `objectId` to the end of the URL to retrieve the users and objects with similar rankings as the given one.
-
-To get the users with similar rankings as the given user:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'startPosition=0' \
- --data-urlencode 'maxResultsCount=20' \
- --data-urlencode 'selectKeys=username,club' \
- --data-urlencode 'includeKeys=club' \
- https://{{host}}/1.1/leaderboard/leaderboards/user//ranks/
-```
-
-See [Retrieving Scores Within a Scope](#retrieving-scores-within-a-scope) for the meanings of the parameters.
-The response is similar to that for [retrieving-scores-within-a-scope](#retrieving-scores-within-a-scope).
-
-```json
-{
- "results": [
- {
- "statisticName": "wins",
- "statisticValue": 3,
- "rank": 2,
- "user": {...}
- },
- {
- "statisticName": "wins",
- "statisticValue": 2.5,
- "rank": 3,
- "user": {
- "__type": "Pointer",
- "className": "_User",
- "username": "kate",
- "club": {
- "objectId": "60f78f98d9f1465d3b1da12d",
- "name": "board games",
- "updatedAt": "2021-07-21T03:08:08.692Z",
- "createdAt": "2021-07-21T03:08:08.692Z",
- },
- "objectId": "60d2faa99be3183623000001"
- }
- },
- {
- "statisticName": "wins",
- "statisticValue": 2,
- "rank": 4,
- "user": {...}
- }
- ],
- "count": 500
-}
-```
-
-To get the objects with similar rankings as the given object:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'startPosition=0' \
- --data-urlencode 'maxResultsCount=2' \
- --data-urlencode 'selectKeys=name,image' \
- --data-urlencode 'includeKeys=image' \
- --data-urlencode 'count=1' \
- https://{{host}}/1.1/leaderboard/leaderboards/object//ranks/
-```
-
-To get the entities with similar rankings as the given entity:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'startPosition=0' \
- --data-urlencode 'maxResultsCount=2' \
- --data-urlencode 'count=1' \
- https://{{host}}/1.1/leaderboard/leaderboards/entity//ranks/
-```
-
-## Video Tutorials
-
-You can refer to the video tutorial:[How to Integrate Leaderboard in Games](https://www.bilibili.com/video/BV1ZN411s7Jj/) to learn how to access leaderboard in Untiy projects.
-
-For more video tutorials, see [Developer Academy](https://developer.taptap.cn/tds-tutorials/list). As the SDK features are constantly being improved, there may be inconsistencies between the video tutorials and the new SDK features, so the current documentation should prevail.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/_category_.json
deleted file mode 100644
index 234726994..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "多人在线对战",
- "collapsed": true,
- "position": 20
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/_category_.json
deleted file mode 100644
index 7bf7181d3..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "Client Engine",
- "collapsed": true,
- "position": 4
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/client-engine.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/client-engine.mdx
deleted file mode 100644
index 1e371ea09..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/client-engine.mdx
+++ /dev/null
@@ -1,111 +0,0 @@
----
-title: Client Engine Overview
-sidebar_label: Overview
-sidebar_position: 1
----
-
-import { Conditional } from "/src/docComponents/conditional";
-
-:::info
-**Before reading this document, please read the [Multiplayer Service Features](/sdk/multiplayer/features/) and [MasterClient](/sdk/multiplayer/guide/js/#masterclient) to understand the architecture you can use for your game when developing features with Multiplayer.**
-:::
-
-## The problem solved by Client Engine
-
-The multiplayer service (i.e. the multiplayer service in the figure below) is a good solution to the problem of abstracting rooms and exchanging messages between players in a room. Let's take the game Rock, Paper, Scissors as an example, the game flow looks like this:
-
-![image](/img/client-engine/client-engine1.png)
-
-where the multiplayer service only plays a role of message relay, and for the sake of discussion we can simplify this diagram a bit (the dotted line represents the message being relayed through the multiplayer service):
-
-![image](/img/client-engine/client-engine2.png)
-
-This process is simple, but there are a few problems:
-
-1. All players have the same god view: they can see all states, and the player who throws a punch later (like B in the picture) can throw a punch according to A's choice to get a sure win.
-2. The final result is reported by the client, and the client can fake the result.
-3. A, as the master client, can manipulate some operations involving randomness, such as shuffling cards, rolling dice, etc. (There is no similar mechanism in Rock, Paper, Scissors).
-
-Different types of games have different tolerances for these problems. Without changing the flow of the diagram above, each of these problems can be solved in its own way. However, the Client Engine solution attempts to solve these problems by taking a radical step away from them: running the MasterClient on the server side. In the Client Engine scenario, the flow of the game is as follows:
-
-![image](/img/client-engine/client-engine3.png)
-
-In this process, the MasterClient running on the server is the only referee with a God's eye view. All players exchange information with the MasterClient, and the MasterClient will only synchronize some information with the client (e.g. it will only tell B that A threw a punch, but what was thrown is unknown). The game logic (including randomness, win/loss decisions) and the reporting of final results are performed on the server.
-
-**The Multiplayer service is the foundation. The player client does not communicate directly with the MasterClient. The dotted lines in the diagram indicate that messages are still routed through the Multiplayer service.**
-
-## Client Engine Introduction
-
-Client Engine is a client-hosting solution for multiplayer online games. The [Multiplayer Service](/sdk/multiplayer/features/) provides a MasterClient mechanism to control the game logic: MasterClient is a special client that receives and processes all events and messages in the game, processes them in real time, and then sends the results to the other game clients, controlling the execution of the game. Developers can develop a complete set of MasterClient logic based on the Multiplayer SDK, and then host such a client in the Client Engine, saving the burden of deploying and maintaining the program. See the picture below:
-
-![image](/img/client-engine/structure.png)
-
-In addition to hosting MasterClient, developers can also do the following things in Client Engine:
-
-* Host regular virtual players to increase the fun and activity of the game.
-* Customise the REST API to develop additional logic.
-
-The benefits of hosting game logic in the Client Engine include:
-
-* Reduced network latency. The game running process involves frequent message interactions among game players, the Multiplayer cloud, and MasterClient. Client Engine and the Multiplayer cloud are in the same physical network, which greatly reduces the transmission delay caused by the public network.
-* Client Engine provides perfect log collection, status monitoring, load balancing and automatic fault recovery mechanism, which can provide higher stability guarantee.
-* Client Engine provides a huge resource pool, which can quickly respond to the temporary and sudden expansion demand of a single game product without having developers manually adjust the instances. Expansions are done automatically.
-
-## Documentation and Demos
-
-For more information on using the Client Engine, please refer to the documentation:
-
-* [Client Engine Quick Start - Node.js](/sdk/multiplayer/client-engine/quick-start-node/) describes how to get started with your first project, how to develop and debug locally, and how to deploy to the cloud.
-* [Your First Client Engine Game - Node.js](/sdk/multiplayer/client-engine/first-game-node/) is a tutorial that will help you get started with a rock-paper-scissors guessing game using the Client Engine. After completing this tutorial you will have a basic understanding of the Client Engine process.
-* [Client Engine Development Guide - Node.js](/sdk/multiplayer/client-engine/guide-node/) provides an in-depth explanation of the Client Engine SDK based on the first project.
-
-Example demo:
-
-* [Turn-based Demo](/sdk/multiplayer/client-engine/demo/#turn-based-demo).
-
-## Price
-
-### Developer Edition
-
-Developer Edition provides a trial edition for developers to use free of charge:
-
-* Free 100 CCU and 50% CPU.
-* No staging environment provided.
-* No auto-scaling and load balancing.
-* Forced hibernation.
-
-Hibernation policy:
-
-Standard instances do not hibernate.
-
-Trial instances enforce the following hibernation policies:
-
-* If the application has had no external requests in the last half hour, it hibernates.
-* If a new external request is made after the hibernation, the instance is started immediately. The response time for the first request is 5 to 30 seconds (depending on the instance startup time), and the response time for subsequent requests returns to normal.
-* Forced hibernation: The instance will be forced into hibernation if the total number of hours of operation in the last 24 hours exceeds 18 hours. At this point, new requests will receive a 503 error response code, which can be viewed in **Cloud Services Console > Play > Client Engine > Statistics**.
-
-If you don't want the service to be interrupted due to trial instances in the staging environment being forced to hibernate, or if you need multiple instances to fully simulate the production environment, you can purchase standard instances in the trial environment as needed.
-
-### Business Edition
-
-With Business Edition, you can upgrade from the trial version to the standard version via the console. Standard version provides a staging environment, supports automatic scaling and load balancing, and does not hibernate.
-
-Standard version is scaled and billed by Compute Unit. A Compute Unit contains 100 CCU and 50% CPU and we will automatically add more Compute Units if any of them gets exhausted. For example, if the Client Engine is using 80 CCU and 90% CPU at a particular time, the system will allocate 2 units.
-
-
-
-The system will charge according to the daily peak consumption of computing units. The price of a single computing unit can be found on the [official website](https://developer.taptap.io/product-intro/price). For example, if an application on a China node uses up to 2 computing units on a given day, the charge for that day will be 2 * computing unit price of China.
-
-
-
-
-
-The system will charge according to the daily peak consumption of computing units. The price of a single computing unit can be found on the [official website](https://developer.taptap.io/product-intro/price). For example, if an application on a China node uses up to 2 computing units on a given day, the charge for that day will be 2 * computing unit price of China.
-
-
-
-
-
-:::info
-Note: After upgrading to the Standard version, the service will be provided and billed at a minimum of 1 compute unit, regardless of whether it is used or not.
-:::
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/demo.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/demo.mdx
deleted file mode 100644
index 4f67c9ab8..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/demo.mdx
+++ /dev/null
@@ -1,31 +0,0 @@
----
-title: Game Demo Example
-sidebar_label: Demo
-sidebar_position: 5
----
-
-This is a collection of game-related demos that you can use when developing your own projects.
-
-## Multiplayer
-
-### Round Robin Battle Demo
-
-This demo is implemented with [Multiplayer](/sdk/multiplayer/features/) and [Client Engine](/sdk/multiplayer/client-engine/). The language used is JavaScript. It took about 7 days to implement all server-side and client-side code. The main features are: quick start, character attribute setting, in-room battle, etc. For details, please click [project link](https://github.com/leancloud/multiplayer-turn-based-game-demo).
-
-### Real-time battle demo
-
-This demo is a streamlined version of the game "Ball Battle" made with [Multiplayer](/sdk/multiplayer/features/), Cocos Creator (JavaScript), and Unity (C#). It took about 8 days to build the project. The demo mainly demonstrates the logic related to mobile synchronization.
-
-For more information:
-
-- [Cocos Creator project](https://github.com/onerain88/BallBattle)
-
-- [Unity project](https://github.com/onerain88/BallBattle-Unity)
-
-## Weakly connected single player game
-
-LeanCloud Anniversary Game is a WeChat game where players can score points by clicking on falling cakes, and the highest scorers can win prizes. The server side mainly uses [Cloud Engine](/sdk/engine/overview/) and [Leaderboard](/sdk/leaderboard/features/). For details, please click [project link](https://github.com/leancloud/LeanCloudBirthday).
-
-Scan the code with WeChat to try the game:
-
-![image](/img/client-engine/leancloud-birthday-game.jpg)
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/first-game-node.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/first-game-node.mdx
deleted file mode 100644
index 7659882e1..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/first-game-node.mdx
+++ /dev/null
@@ -1,474 +0,0 @@
----
-title: Your first game with Client Engine - Node.js
-sidebar_label: Your first game
-sidebar_position: 3
----
-
-This document will help you get up to speed on implementing a rock-paper-scissors guessing game with the Client Engine. After completing this tutorial you will have a basic understanding of the Client Engine process.
-
-## Preparing your first project
-
-The game is divided into two parts: the server side, which is implemented by the Client Engine, and the client side, which is a simple web page. In this tutorial we will focus on teaching you how to write the Client Engine code step by step. For the client-side code please refer to the sample project.
-
-### Client Engine Project
-
-Please read [Client Engine Quickstart: Running and Deploying a Project](/sdk/multiplayer/client-engine/quick-start-node/) to get the initial project and learn how to run and deploy a project locally.
-
-`./src` contains the following source files:
-
-```
-├── configs.ts // Configuration files
-├── index.ts // The project entry point
-├── reception.ts // Reception class implementation file; subclass of GameManager; responsible for managing Game; contains the custom methods to create Game
-└── rps-game.ts // RPSGame class implementation file; a subclass of Game, in which the specific logic of the guessing game is written
-```
-
-The `Game` and `GameManager` in this project use the functionality of the Client Engine SDK. Please refer to the [Client Engine Developer's Guide](/sdk/multiplayer/client-engine/guide-node/) for more details on how to use the SDK.
-
-You can get a feel for the project by starting with the `index.ts` file, which is the entry point to the project, and defines a web API called `/reservation` using the express framework to issue new room names to clients when quick-starting new games.
-
-`reception.ts` and `rps-game.ts` contain all the code for this tutorial. You can choose to make a copy of these two files, empty them, and write your own code based on this document, or view the code already written for comparison.
-
-### Client Project
-
-[Click here to download the client project](https://github.com/leancloud/client-engine-demo-webapp). **Open `config.ts` in `./src`, change the appId and appKey to your own information**, and follow the README to start the project and observe the interface changes. The game-related logic is in `./src/components`. You can open this file to view the code if needed.
-
-## Core Process
-
-In the Multiplayer service, the room is created by a MasterClient, so in this game, each room is created by a MasterClient managed by the Client Engine that calls the interfaces related to the Multiplayer service. There are multiple MasterClients in the Client Engine, and each MasterClient manages the game logic in its own room.
-
-The core logic of this game is: **MasterClient in Client Engine and player client join in the same room. In the communication process, the MasterClient controls the logic of the game.** The specific steps are as follows:
-
-1. The player client connects to the [Multiplayer service](/sdk/multiplayer/features/) and requests the `/reservation` interface provided by the Client Engine to start the game quickly.
-2. Each time the Client Engine receives a request, it will check if there is an available room, if there is, it will return the existing roomName to the client, if there is not, it will create a new MasterClient and create a new room, and return the roomName to the client.
-3. The Client joins the room using the roomName returned by the Client Engine.
-4. MasterClient and Client are in the same room. Each time the Client throws a punch it will send a message to the MasterClient, the MasterClient will forward the message to other Clients, and finally judge the game result.
-5. The MasterClient decides that the game is over. The Client leaves the room and the Client Engine destroys the game.
-
-## Code development
-
-### Customize Game
-
-Our goal is to get the MasterClient and Client into the same room. The first step is to prepare the room in the Client Engine. In the Client Engine SDK, each room corresponds to a `Game` object, and each `Game` object corresponds to its own MasterClient. Next, we create a subclass `RPSGame` that inherits from `Game`, and write the in-room logic for the guessing game in `RPSGame`.
-
-To initialize the custom `RPSGame` in `rpg-game.ts`:
-
-```js
-import { Game } from "@leancloud/client-engine";
-import { Event, Play, Room } from "@leancloud/play";
-export default class RPSGame extends Game {
- constructor(room: Room, masterClient: Play) {
- super(room, masterClient);
- }
-}
-```
-
-### Managing the Game
-
-In the Client Engine SDK, the `GameManager` is responsible for creating and destroying the `Game`. For more information, please refer to the [Client Engine Developer's Guide](/sdk/multiplayer/client-engine/guide-node/). In this document, we can utilize the `GameManager` management functions through simple configuration.
-
-#### Customizing the GameManager
-
-First, create a subclass `Reception` by inheriting from `GameManager`. We can use the methods provided by `GameManager` to assist us in writing our own logic.
-
-Initialize the custom `Reception` in the `reception.ts` file:
-
-```js
-import { Game, GameManager, ICreateGameOptions } from "@leancloud/client-engine";
-export default class Reception extends GameManager {
-
-}
-```
-
-This custom class `Reception` is used to manage `Game` objects of type T, which in the actual game will be instances of your custom `Game` type. Next, we use the `GameManager` method in `reception` to implement our own custom logic: quick start.
-
-#### Implementing Logic: Quick Start
-
-The implemented quick start logic finds and returns a random room with available seats to the client. If there is no available room in the current Client Engine instance, the logic creates a new room and returns it to the client. To enable the [Entry API](#entry-api-quick-start) to call the implemented logic, a custom method called `makeReservation()` is written in the `Reception` class.
-
-```js
-import { Game, GameManager, ICreateGameOptions } from "@leancloud/client-engine";
-export default class Reception extends GameManager {
-
- /**
- * Reserve a game for the given player. If no game is available, a new one will be created.
- * @param playerId The ID of the player who made the reservation.
- * @return The room name of the successfully reserved game.
- */
- public async makeReservation(playerId: string) {
- let game: T;
- const availableGames = this.getAvailableGames();
- if (availableGames.length > 0) {
- game = availableGames[0];
- this.reserveSeats(game, playerId);
- } else {
- game = await this.createGame(playerId);
- }
- return game.room.name;
- }
-
-}
-```
-
-In this code, we call `GameManager`'s `getAvailableGames()` to get the `Game` objects managed by the current Client Engine instance:
-
-
-* If there are `Game` instances with empty seats in the room, use the `reserveSeats()` method of the `GameManager` to take the seats for the player and return the roomName.
-* If all `Game` rooms are full, use `createGame()` method of `GameManager` to create a new room and return roomName.
-
-#### Implementing Logic: Create New Game
-
-
-If you want to create a room yourself and then invite your friends to join the room, you can write a method for creating a new game in `reception` for [Entry API](#entry-entry-api-creating-a-new-game) to call. Similarly, the `createGame()` method in our custom `createGameAndGetName()` method is provided by the `GameManager` in the SDK.
-
-```js
-export default class Reception extends GameManager {
-
- public async makeReservation(playerId: string) {
- ......
- }
-
- /**
- * Creates a new game.
- * @param playerId The player ID of the reservation.
- * @param options Some configuration items that can be specified when creating a new game.
- * @return The room name of the game being created.
- */
- public async createGameAndGetName(playerId: string, options?: ICreateGameOptions) {
- const game = await this.createGame(playerId, options);
- return game.room.name;
- }
-
-}
-```
-
-#### Binding GameManager and Game
-
-Once both `Reception`, a subclass of `GameManager`, and `RPSGame`, a subclass of `Game`, are initialized, we need to provide `RPSGame` to `Reception` within the entry point of the entire project and have `Reception` manage `RPSGame`.
-
-The `index.ts` file contains a method which creates the `Reception` object. In this method, the first parameter is provided with `RPSGame`. Should your custom `Game` class have a different name, you can substitute `RPSGame` with your custom `Game` class.
-
-```js
-import PRSGame from "./rps-game";
-const reception = new Reception(
- PRSGame,
- APP_ID,
- APP_KEY,
- {
- concurrency: 2,
- },
-);
-```
-
-Once configured in this section, `reception` will create and manage `PRSGame` along with the corresponding MasterClient when it is appropriate.
-
-#### Load Balancing Configuration
-
-As the logic in `GameManager` is invoked directly by external requests, load balancing must be configured for `GameManager` at the entry point. For more details on load balancing, please consult the [Client Engine Developer's Guide](/sdk/multiplayer/client-engine/guide-node/#load-balancing). In this section, we can refer to the `index.ts` file code to learn how to configure it.
-
-```js
-import { ICreateGameOptions,LoadBalancerFactory } from "@leancloud/client-engine";
-
-// Create the object responsible for load balancing. No need to change the code here; just copy and paste it.
-const loadBalancerFactory = new LoadBalancerFactory({
- poolId: `${APP_ID.slice(0, 5)}-${process.env.LEANCLOUD_APP_ENV || "development"}`,
- redisUrl: process.env.REDIS_URL__CLIENT_ENGINE,
-});
-
-// Configure load balancing with reception and our custom method makeReservation.
-loadBalancerFactory.bind(reception, ["makeReservation", "createGameAndGetName"])
-```
-
-Now that the `reception` for managing the `RPSGame` has been prepared, we'll start writing the specific in-room game logic.
-
-### Setting the number of players in a room
-
-In this guessing game, we've set it so that only two players are allowed to play, and no new players are allowed to enter the room when there are already two players. You can set the `Game`'s static property `defaultSeatCount` like this:
-
-```js
-export default class RPSGame extends Game {
- public static defaultSeatCount = 2;
-}
-```
-
-After configuring, the Client Engine initial project will limit the number of players in a room every time it requests the Multiplayer service to create a room, based on the value set here.
-
-For more information on setting the number of players in a room, please refer to the [Client Engine Developer's Guide](/sdk/multiplayer/client-engine/guide-node/#setting-the-number-of-players-in-a-room).
-
-### The MasterClient and the client enter the same room
-
-Once the basic configuration of the `Game` is complete, both the MasterClient and Client are now able to enter the same room.
-
-#### Entry API Quick Start
-
-When a client makes a request to the `/reservation` API endpoint specified in the `index.ts` file, the endpoint will invoke the `makeReservation()` method in `Reception`, which helps the client start the game quickly.
-
-```js
-app.post("/reservation", async (req, res, next) => {
- try {
- const {
- playerId,
- } = req.body as {
- playerId: any
- };
- if (typeof playerId !== "string") {
- throw new Error("Missing playerId");
- }
- debug(`Making reservation for player[${playerId}]`);
- // Call the makeReservation() method we prepared in the Reception class.
- const roomName = await reception.makeReservation(playerId);
- debug(`Seat reserved, room: ${roomName}`);
- return res.json({
- roomName,
- });
- } catch (error) {
- next(error);
- }
-});
-```
-
-Clients can call this API to get a quick start. Sample code using this interface is as follows **(not Client Engine code)**:
-
-```js
-// Here the client calls the `/reservation` interface implemented in the Client Engine over HTTP.
-const { roomName } = await (await fetch(
- `${CLIENT_ENGINE_SERVER}/reservation`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify({
- playerId: play.userId,
- })
- }
-)).json();
-// Join the room
-return play.joinRoom(roomName);
-```
-
-When the client calls `/reservation` and joins the room successfully, it means the Client and MasterClient are in the same room, and you can start the game when there are enough people in the room.
-
-The code to call `/reservation` is already written in the client project, so you don't need to write the code by yourself. You can check the related code in `/src/components/Lobby.vue`.
-
-#### Entry API Creating a New Game
-
-This entry API is written in the same way as Quick Start, so I won't repeat the instructions, but you can refer to the `/game` method in the index.ts file.
-
-### Announcing the start of a game
-
-In this project, we can start the game when a room is full. We can announce the start of the game in the Game's room-full event:
-
-```js
-import { AutomaticGameEvent, Game, watchRoomFull } from "@leancloud/client-engine";
-import { Play, Room } from "@leancloud/play";
-
-enum Event {
- Start = 10,
-};
-
-@watchRoomFull()
-export default class RPSGame extends Game {
- public static defaultSeatCount = 2;
- constructor(room: Room, masterClient: Play) {
- super(room, masterClient);
- // Listen for the ROOM_FULL event and call the `start() method` when it receives it.
- this.once(AutomaticGameEvent.ROOM_FULL, this.start);
- }
-
- protected start = async () => {
- // Mark the room as closed
- this.masterClient.setRoomOpened(false);
- // Broadcast the game start event to the client
- this.broadcast(Event.Start);
- }
-}
-```
-
-In this code, the `watchRoomFull` decorator causes `Game` to throw the `AutomaticGameEvent.ROOM_FULL` event when the room is full, where we choose to call our custom `start` method. In the `start` method we open the room and broadcast the start of the game to all clients.
-
-At this point, you can start the current Client Engine project, launch the client and open two client web pages, click "Quick Start" on the interface, and observe that the first interface in which you clicked "Quick Start" shows the log: `xxxx joined the room`.
-
-### Guessing Logic
-
-Now let's start developing the in-game logic. The steps are as follows:
-
-1. Player A selects a gesture and sends a punch event to the MasterClient.
-2. MasterClient receives the event and forwards it to Player B.
-3. Player B receives the event forwarded by MasterClient, and the interface displays: the opponent has chosen.
-4. Player B selects gesture and sends punch event to MasterClient.
-5. MasterClient receives the event and forwards it to Player A.
-6. Player A receives the event forwarded by MasterClient, and the interface displays: opponent has chosen.
-7. MasterClient finds that both players have thrown punches, so it judges the result, announces the answer, and declares the end of the game.
-
-The interaction between these three players can be illustrated by this diagram:
-
-![image](/img/client-engine/rps-game-flow.png)
-
-Next, we break down each step and write the code:
-
-#### Player A selects a gesture and sends a punch event to the MasterClient
-
-This part of the code is client-side only and **you don't need to write it in the Client Engine**. You can find the code in the client-side project's `./src/components/Game.vue`.
-
-```js
-enum Event {
- Start = 10,
- Play = 11,
-};
-choices = ["✊", "✌️", "✋"];
-
-// When the user makes a selection, we send the index of the corresponding option to the server.
-play.sendEvent(Event.Play, {index}, {receiverGroup: ReceiverGroup.MasterClient});
-```
-
-#### MasterClient receives the event and forwards it to Player B
-
-This part of the code is written in Client Engine. You can write it in your own `RPSGame` based on the sample code below. We register the custom event in the `start` method, and when we receive the `play` event, we clear the contents of player A's action and forward it to player B.
-
-```js
-protected start = async () => {
- ......
- // Receiving custom events
- this.masterClient.on(PlayEvent.CUSTOM_EVENT, ({ eventId, eventData, senderId }) => {
- if (eventId === Event.Play) {
- // Receive events from other players and forward the events
- this.forwardToTheRests({ eventId, eventData, senderId }, (eventData) => {
- return {}
- })
- }
- });
-}
-```
-
-In this code, the MasterClient object in Game registers a custom event for multiplayer games, which is triggered when Player A sends the `play` event to the MasterClient. We use Game's forward event method `forwardToTheRests()` in this event. The first parameter of this method is the original event, and the second parameter is the eventData data handler of the original event. We change the original eventData data, that is the `{index}` sent by player A, to empty data, so that when player B receives the event, he can't know the details of player A's action.
-
-#### The player B receives the event forwarded by the MasterClient and the interface shows that the opponent has selected.
-
-This part of the code is client-side and **you don't need to write it in the Client Engine**. You can find it in the client-side project's `./src/components/Game.vue`.
-
-```js
-play.on(PlayEvent.CUSTOM_EVENT, ({ eventId, eventData, senderId }) => {
- ......
- switch (eventId) {
- ......
- case Event.Play:
- this.log(`The opponent has chosen`);
- break;
- .....
- }
-});
-```
-
-#### Player B selects a gesture and sends a punch event to MasterClient
-
-This part of the logic is the same as "Player A selects a gesture and sends a punch event to MasterClient" above, and uses the same part of the code. You can find the code in the client project's `./src/components/Game.vue`.
-
-#### MasterClient receives an event and passes it on to Player A
-
-This part of the logic is the same as "MasterClient receives event and forwards event to player B" above, and uses the same part of the code, so you don't need to write any additional code in the Client Engine.
-
-#### Player A receives the event forwarded by the MasterClient and the interface shows: the opponent has selected
-
-This part of the logic is the same as the "Player A receives event forwarded by MasterClient and the interface displays: opponent selected" above, using the same part of the code. You can find the code in the client project's `./src/components/Game.vue` in the client project.
-
-At this point you can run the project, open both interfaces for guessing, and observe that both players' actions are synchronised to their respective interfaces, but each does not know what the other player has chosen.
-
-#### MasterClient detects that both players have thrown a punch, determines the result, announces the answer, and declares the game over
-
-Each time the MasterClient receives a player's choice event, we need to store the player's choice and determine if both players have made their choices:
-
-```js
-protected start = async () => {
- ......
- // [this.player[0]'s choice, this.player[1]'s choice]. When neither player has a choice, it is assumed that both players have a choice of -1
- const choices = [-1, -1];
-
- this.masterClient.on(PlayEvent.CUSTOM_EVENT, ({ eventId, eventData, senderId }) => {
- if (eventId === Event.Play) {
- // Receive events from other players and forward events
- ......
- // Stores the current player's selection
- if (this.players[0].actorId === senderId) {
- // If it's player[0], store it in choices[0].
- choices[0] = eventData.index;
- } else {
- // If it's player[1], store it in choices[1].
- choices[1] = eventData.index;
- }
- }
- });
-}
-```
-
-In the above code, we construct a choice of type Array to store the player's choices, and store the user's choices when the punch event is received, then we determine whether both players have made their choices, and if they have, we broadcast the result of the game, and broadcast the end of the game:
-
-```js
-enum Event {
- Start = 10,
- Play = 11,
- Over = 20,
-};
-......
-protected start = async () => {
- ......
- // [this.player[0]'s choice, this.player[1]'s choice]. When neither player has a choice, it is assumed that both players have a choice of -1
- const choices = [-1, -1];
-
- this.masterClient.on(PlayEvent.CUSTOM_EVENT, ({ eventId, eventData, senderId }) => {
- if (eventId === Event.Play) {
- // Receive events from other players and forward events
- ......
- // Store the current player's selection
- ......
- // Check if both players have already made their choices
- if (choices.every((choice) => choice > 0)) {
- // Both players have made their choices. The game is over, and the result is broadcast to the client.
- const winner = this.getWinner(choices);
- this.broadcast(Event.Over, {
- choices,
- winnerId: winner ? winner.userId : null,
- });
- }
- }
- });
-}
-
-```
-
-In the above code, the `getWinner()` method is used to get the result of the game. This is our customized method to determine the winner. You can copy and paste the code below directly into your own `RPSGame` file:
-
-```js
-// The client's array of punches is :[✊, ✌️, ✋].
-// ✊ (index is 0) beats✌️ (index is 1), so wins[0] = 1, and so on
-const wins = [1, 2, 0];
-
-@watchRoomFull()
-export default class RPSGame extends Game {
- ......
-
- /**
- * Calculate the winner based on the player's choices
- * @return returns the winning Player, or null for a tie
- */
- private getWinner([player1Choice, player2Choice]: number[]) {
- if (player1Choice === player2Choice) { return null; }
- if (wins[player1Choice] === player2Choice) { return this.players[0]; }
- return this.players[1];
- }
-}
-```
-
-The client receives the MasterClient broadcast end event and then displays the corresponding result on the interface. Here the basic logic has been developed. You can run the project, open the two pages, and happily start your own battle with yourself.
-
-### Leaving the room
-
-When all the clients leave the room, `GameManager` will help us to destroy the empty room, so we don't need to write this part of code in our game.
-
-### RxJS
-
-When you look at the sample demo, you will see that the code is a bit more compact compared to the code in this document, because the sample demo uses RxJS. If you are interested, you can study the [RxJS](https://rxjs-dev.firebaseapp.com/) and the [API documentation](https://rxjs-dev.firebaseapp.com/api) of the related interfaces by yourself.
-
-## Development Guide
-
-After you have developed the guessing game step by step according to the instructions in this document, you must have a preliminary feeling about the Client Engine SDK and the initial project. Now you can refer to the [Client Engine Developer's Guide](/sdk/multiplayer/client-engine/guide-node/) to learn more about the structure and usage of Client Engine.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/guide-node.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/guide-node.mdx
deleted file mode 100644
index 0f915e8f9..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/guide-node.mdx
+++ /dev/null
@@ -1,386 +0,0 @@
----
-title: Client Engine Developer Guide - Node.js
-sidebar_label: Developer Guide
-sidebar_position: 4
----
-
-Please read [Client Engine Quick Start - Node.js](/sdk/multiplayer/client-engine/quick-start-node/) and [Your First Client Engine Game](/sdk/multiplayer/client-engine/first-game-node/) to get an initial idea of how to develop a game using a starter project. This document will build on the initial project to explain the Client Engine SDK in depth.
-
-The Client Engine starter project relies on the Client Engine SDK, which is a wrapper around the Multiplayer SDK to help you write server-side game logic. You can install the dependencies by following the [quickstart guide](/sdk/multiplayer/client-engine/quick-start-node/).
-
-## Components
-
-The SDK provides the following components:
-
-* **`Game` :** Responsible for the specific logic of the game in the room. Client Engine maintains a number of game rooms, each of which is a Game instance, i.e., each Game instance corresponds to a unique Play Room and MasterClient. Logic in the game rooms is controlled by the code in the Game, so **the logic of the game in the room must be inherited from this class**.
-* **`GameManager` :** Responsible for creating, managing and distributing specific Game objects. The management and destruction of Game are handled by the SDK, you don't need to write additional code.
-
-### GameManager
-
-#### GameManager instantiation
-
-`GameManager` will help you to create, manage and destroy Game automatically, so you need to instantiate `GameManager` when your project starts. The sample code is shown below:
-
-##### Customising GameManager
-
-First of all, you need to customise a Class inherited from `GameManager`, such as `SampleGameManager` in the sample code:
-
-```js
-import { Game, GameManager, ICreateGameOptions } from "@leancloud/client-engine";
-export default class SampleGameManager extends GameManager {
-
-}
-```
-
-##### Custom Methods in GameManager
-
-One of the core uses of the client engine is to create a Game and return the roomName to the client, so in the `SampleGameManager` class we need to write a method to create a `Game` for the web API to use. Like [quick start](/sdk/multiplayer/client-engine/quick-start-node/#implements-the-quick-start-logic) and [create new game](/sdk/multiplayer/client-engine/first-game-node/#implements-the-create-new-game-logic) in the example project. Here's the sample code we'll use to create a new game:
-
-```js
-import { Game, GameManager, ICreateGameOptions } from "@leancloud/client-engine";
-export default class SampleGameManager extends GameManager {
- /**
- * Creates a new game.
- * @param playerId The player ID of the reservation.
- * @param options Some configuration items that can be specified when creating a new game.
- * @return The room name of the game being created.
- */
- public async createGameAndGetName(playerId: string, options?: ICreateGameOptions) {
- const game = await this.createGame(playerId, options);
- return game.room.name;
- }
-}
-```
-
-After writing the custom method, later we also need to configure [load balancing](#load-balancing) for the method here. It should be noted that by the requirement of [load balancing system](#load-balancing), `public` methods in `GameManager` subclass must have their parameters and return values be `string`, `number`, `boolean`, `null`, `Object`, or `Array`. In the above code, we can see that the `createGame()` method of `GameManager` returns a `Game`, which does not meet the requirement of load balancing, so we encapsulate it into our own method `createGameAndGetName()` here.
-
-##### Creating GameManager Subclass Objects
-
-Next, create a subclass of `GameManager`. When creating `SampleGameManager`, you need to pass [Custom Game](#implementing-your-own-game) in the first parameter. Here we use [Sample Demo](/sdk/multiplayer/client-engine/first-game-node/) of the guessing game `RPSGame`.
-
-```js
-import PRSGame from "./rps-game";
-const gameManager = new SampleGameManager(
- gameConstructor: PRSGame,
- appId: {{appid}},
- appKey: {{appkey}},
- playServer: "https://please-replace-with-your-customized.domain.com",
- concurrency: 2,
-);
-```
-
-##### Setting up load balancing
-
-The `GameManager` needs to be configured for load balancing to ensure that the `Game` objects created by the `GameManager` are distributed as evenly as possible to each Client Engine instance. See below for detailed documentation on [load balancing](#load-balancing). Here we will start by explaining how to configure it.
-
-Here we will create a [load balancing](#load-balancing) object and bind the above `gameManager` to it:
-
-```js
-import { ICreateGameOptions,LoadBalancerFactory } from "@leancloud/client-engine";
-
-// Create the object responsible for load balancing. Don't change it; just copy and paste it when you use it.
-const loadBalancerFactory = new LoadBalancerFactory({
- poolId: `${APP_ID.slice(0, 5)}-${process.env.LEANCLOUD_APP_ENV || "development"}`,
- redisUrl: process.env.REDIS_URL__CLIENT_ENGINE,
-});
-
-// Configure load balancing with reception and our custom method makeReservation.
-const loadBalancer = loadBalancerFactory.bind(gameManager, ["createGameAndGetName"]);
-```
-
-In the `bind()` method of `loadBalancerFactory`, the first parameter is the `gameManager` and the second parameter is an array containing the names of the methods that need load balancing, like `["createGameAndGetName"]`.
-
-At this point, the configuration of the `gameManager` is complete, and you can call the relevant method at your own defined Web API like this: `gameManager.createGameAndGetName()`.
-
-#### Creating a Room
-
-In the section [GameManager Instantiation](#gamemanager-instantiation), we used `createGame()` of `GameManager` in a subclass to create a room.
-
-`createGame()` accepts the following parameters:
-
-* playerId: [userId](/sdk/multiplayer/guide/js/#initialisation) of the client initiating the request in the Multiplayer service.
-* createGameOptions (optional): create the room with the specified conditions.
- * roomName (optional): create the room with the specified roomName. For example, if you need to play with your friends, you can use this interface to create a room and then share the roomName with your friends. If you don't care about roomName, you can leave it out.
- * roomOptions (optional): with this parameter, the client can set `customRoomProperties`, `customRoomPropertyKeysForLobby`, and `visible` when requesting the Client Engine to create a room. Please refer to [Create Room](/sdk/multiplayer/guide/js/#creating-a-room) for the description of these three parameters.
- * seatCount (optional): when creating a room, specify how many players are needed for this game. This value needs to be between `minSeatCount` and `maxSeatCount` of [setting the number of players in a room](#setting-the-number-of-players-in-a-room), otherwise the Client Engine will refuse to create the room. If not specified, `defaultSeatCount` is used.
-
-For example, to create a new room with matching conditions, call `createGame()` like this:
-
-```js
-// You can get the playerId and createGameOptions from the request sent by the client.
-const props = {
- level: 2,
-};
-
-const roomOptions = {
- customRoomPropertyKeysForLobby: ['level'],
- customRoomProperties: props,
-};
-
-const createGameOptions = {
- roomOptions
-};
-
-gameManager.createGame(playerId, createGameOptions);
-```
-
-In [your first Client Engine game](/sdk/multiplayer/client-engine/first-game-node/), `reception.ts` writes two custom methods for "QuickStart" and "Create a new game" using `createGame() and invokes relevant logic with the web APIs `/reservation` and `/game` in `index.ts`. If you don't need any customization, you can just use the interfaces in the sample demo with the above parameters.
-
-#### Getting currently available rooms
-
-GameManager provides a `getAvailableGames()` method to retrieve the list of available games in the Client Engine instance where the GameManager object resides. Here, available means that the room still has empty seats. The sample code is as follows.
-
-```js
-var games = gameManager.getAvailableGames();
-```
-
-Note that this method does not fetch all available rooms in the Multiplayer service but only **the available rooms in the current Client Engine instance**. For Client Engine multi-instance load balancing, please refer to [Load Balancing](#load-balancing).
-
-#### Matching
-
-The `GameManager` does not provide a matching mechanism for the time being. If a client only needs to join a room randomly, please refer to the `Quick Start` implementation in [sample project](/sdk/multiplayer/client-engine/first-game-node/). This implementation looks for available rooms or creates rooms in the least loaded instance, and eventually returns to the client the name of a room that can be joined.
-
-If you wish to implement conditional matching, you can implement it like this:
-
-1. The client requests [conditional match](/sdk/multiplayer/guide/js/#randomly-joining-rooms) from the multiplayer service, and if there is a room available, the join-success event is triggered.
-2. If there is no spare room in the Multiplayer service, the client will receive the "join room failed" event. In this event, if the error code is [4301](/sdk/multiplayer/error-code/#4301), then request the Client Engine to create a room.
-3. The Client Engine receives the request, creates the room and returns the roomName to the client. The logic of this part can use the `/game` entry in [sample project](/sdk/multiplayer/client-engine/first-game-node/).
-4. The client gets the roomName returned by the Client Engine, joins the room, and waits for others to join.
-
-The sample code for this process in the client is as follows (**not Client Engine**):
-
-The client first makes a conditional request to the Multiplayer service to join the room:
-
-```js
-const matchProps = {level: 2};
-play.joinRandomRoom({matchProperties: matchProps});
-```
-
-If the multiplayer service has a new room available at this time, you will automatically be added to the new room and the join-room-success event will be triggered.
-
-```js
-play.on(Event.ROOM_JOINED, () => {
- // TODO can do things like jumping to other scenes
-});
-```
-
-If no room is available, the join-room-failed event will be triggered. In this event, the error code [4301](/sdk/multiplayer/error-code/#4301) means there's no room available. Now we can request the Client Engine to create a new room, get the roomName of the new room, and join the new room:
-
-```js
-// Requesting Client Engine to create a room after failing to join a room
-play.on(Event.ROOM_JOIN_FAILED, (error) => {
- if (error.code === 4301) {
- // Setting up to create rooms with matching properties
- const props = {level: 2};
- const options = {customRoomPropertyKeysForLobby: ['level']};
- // The `/game` interface implemented in the Client Engine is called over HTTP.
- const { roomName } = await (await fetch(
- `${CLIENT_ENGINE_SERVER}/game`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify({
- playerId: play.userId,
- options
- })
- }
- )).json();
- // Join the room
- return play.joinRoom(roomName);
- } else {
- console.log(error);
- }
-});
-```
-
-### Game
-
-#### Game lifecycle
-
-1. **Create:** `Game` is managed by `GameManager` in the SDK, which creates a `Game` as appropriate when it receives a request to create a room.
-2. **Run:** After creation, control of the `Game` is transferred from the `GameManager` in the SDK to the `Game` itself. From this moment on, players will join the game room one after another.
-3. **Destruction:** Once all players have left the room, the game is over, and the `Game` hands control back to the `GameManager`, which does the final cleanup, including disconnecting and destroying the masterClient for the room, deleting the `Game` from the list of games it manages, and so on.
-
-#### General Properties of Game
-
-The `Game` class provides the following properties to simplify the implementation of common requirements in implementing game logic. You can easily get the following properties in your own class inheriting `Game`:
-
-* `room` property: the room that the game corresponds to, which is an instance of Room in the Play SDK.
-* `masterClient` property: the masterClient for the game, which is an instance of Play in the Play SDK.
-* `players` property: a list of players that do not contain a masterClient. Note that if you get the list of room members via the `playerList` property of a Play SDK Room instance, it includes the masterClient.
-
-#### General Methods of Game
-
-The Game class encapsulates the following methods on top of the Multiplayer SDK, which allows MasterClient to send custom events more conveniently:
-
-* `broadcast()` method: broadcast custom event to all players. Please refer to [Broadcast Custom Events](#broadcasting-custom-events) for example code.
-* `forwardToTheRests()` method: forward the custom events sent by one player to other players. Please refer to [Forwarding Custom Events](#forwarding-custom-events) for example code.
-
-#### Implementing Your Own Game
-
-To implement your own in-room game logic, you need to create a class that inherits from `Game` to write your own game logic. The sample method is as follows:
-
-```js
-import { Game } from "@leancloud/client-engine";
-export default class SampleGame extends Game {
- constructor(room: Room, masterClient: Play) {
- super(room, masterClient);
- }
-}
-```
-
-#### Setting the number of players in a room
-
-The number of players here refers to the number of players excluding the MasterClient, and according to the limitations of the Multiplayer service, the maximum number of players cannot exceed 9.
-
-In `Game`, you need to specify `defaultSeatCount` static attribute as the default number of players, and the Client Engine will request the multiplayer service to create a room according to this value. For example, if you need 3 players to play Landlord, you can set it like this:
-
-```js
-export default class SampleGame extends Game {
- public static defaultSeatCount = 3; // Maximum 9
-}
-```
-
-If your game requires a certain number of players, in addition to setting `defaultSeatCount`, you need to use the `minSeatCount` static attribute to limit the minimum number of players and the `maxSeatCount` static attribute to set the maximum number of players. For example, Triple Triad requires a minimum of 2 players and a maximum of 8 players to play, but the default is 5 players, so you can set it like this:
-
-```js
-export default class SampleGame extends Game {
- public static minSeatCount = 2;
- public static maxSeatCount = 8; // Maximum 9
- public static defaultSeatCount = 5;
-}
-```
-
-In the [Create Room](#creating-a-room) interface, you can dynamically override `defaultSeatCount` with the `seatCount` parameter in the client request.
-
-You can optionally configure the [room full event](#room-full-event) to fire when the room reaches `seatCount`; if your client does not specify `seatCount`, the `defaultSeatCount` value will be used for the room full event.
-
-#### Join Room Event
-
-When a client successfully joins a room, the MasterClient in the Client Engine will receive the [new player join event](/sdk/multiplayer/guide/js/#new-player-join-event). If you need to listen to this event, you can write the code to listen to it in the `constructor()` method in your custom `Game`.
-
-```js
-import { Game } from "@leancloud/client-engine";
-export default class SampleGame extends Game {
- constructor(room: Room, masterClient: Play) {
- super(room, masterClient);
- this.masterClient.on(Event.PLAYER_ROOM_JOINED, () => {
- console.log('Someone\'s coming');
- });
- }
-}
-```
-
-#### Room Full Event
-
-When the number of people in a room satisfies the room full logic of [set the number of players in the room](#setting-the-number-of-players-in-a-room), the `watchRoomFull` decorator lets you receive the `AutomaticGameEvent.ROOM_FULL` event thrown by the Game, where you can write the appropriate game logic, such as closing the room, broadcasting the start of the game to the Client:
-
-```js
-import { AutomaticGameEvent, Game, watchRoomFull } from "@leancloud/client-engine";
-
-enum Event {
- GameStart = 15,
-};
-
-@watchRoomFull()
-export default class SampleGame extends Game {
- constructor(room: Room, masterClient: Play) {
- super(room, masterClient);
- // Listen for the ROOM_FULL event and call the `start() method` when it receives it.
- this.once(AutomaticGameEvent.ROOM_FULL, this.start);
- }
-
- protected start = async () => {
- // Write the logic for when your room is full here.
- // Mark the room as no longer available
- this.masterClient.setRoomOpened(false);
- // Broadcast the game start event to the client
- this.broadcast(Event.GameStart);
- }
-}
-```
-
-#### Broadcasting custom events
-
-In [Room Full Event](#room-full-event), `Game` broadcasts the start of the game to all members of the room:
-
-```js
-enum Event {
- GameStart = 15,
-};
-this.broadcast(Event.GameStart);
-```
-
-You can also broadcast events with some data:
-
-```js
-enum Event {
- GameStart = 15,
-};
-const gameData = {someGameData};
-this.broadcast(Event.GameStart, gameData);
-```
-
-At this point the client's [receive custom event](/sdk/multiplayer/guide/js/#receiving-custom-events) method will be triggered, and if it finds out it's a `game-start` event, the client can show the start of the matchmaking on the UI.
-
-#### Forwarding custom events
-
-MasterClient can forward events from one client to other clients, and process data while doing so:
-
-```js
-enum Event {
- SomeEvent = 15,
-};
-this.forwardToTheRests(event, (eventData) => {
- // Preparing data to be forwarded
- const actUserId = event.senderId;
- const result = {actUserId};
- return result;
- // Event.SomeEvent is the ID of the custom event, or the ID of the original event if omitted.
-}, Event.SomeEvent)
-```
-
-In this code, the `event` parameter is the original event sent by some client, and `eventData` is the data of the original event, which you can manipulate when forwarding the event to other clients, e.g., to erase or add some information. After MasterClient sends the event, the client's [Receive Custom Event](/sdk/ multiplayer/guide/js/#ReceiveCustomEvent) is triggered.
-
-#### Communications between the MasterClient and clients
-
-In addition to the [Broadcast Custom Events](#broadcasting-custom-events) and [Forward Custom Events](#forwarding-custom-events) provided in the initial project above, you can still use [Custom Attributes](/sdk/multiplayer/guide/js/#custom-attributes-and-synchronisation) and [Custom Events](/sdk/multiplayer/guide/js/#custom-events) in the Multiplayer service for communication.
-
-In addition, `Game` provides the following [RxJS](http://reactivex.io/rxjs) methods to stream events and streamline your code and logic.
-
-* `getStream()` method: get the stream of custom events sent by the player, which is an Observable object in RxJS. Please refer to [API Documentation](https://leancloud.github.io/client-engine-nodejs-sdk/classes/game.html#getstream) for interface description.
-* `takeFirst()` method: get the stream of the first custom event sent by the player with the specified condition counting from now. Returns an Observable object in RxJS. Please refer to the [API documentation](https://leancloud.github.io/client-engine-nodejs-sdk/classes/game.html#takefirst) for the interface description.
-
-Note that the above two methods require you to know [RxJS](http://reactivex.io/rxjs) in order to use them. If you don't know [RxJS](http://reactivex.io/rxjs), you can still use the [event methods](/sdk/multiplayer/guide/js/#custom-events) for communication.
-
-#### Game Over
-
-When all players have left, `GameManager` will automatically destroy the current room and the associated MasterClient for you; at this point, if you have no other logic to do, you don't need to concern yourself with this section of the documentation. If you want to do some cleanup yourself, such as saving user data, you can use the `autoDestroy` decorator, which will automatically trigger the `destroy()` method in the `Game` subclass when all the players have left, and you can write the relevant logic in this method.
-
-```js
-import { autoDestroy, Game } from "@leancloud/client-engine";
-
-@autoDestroy()
-export default class SampleGame extends Game {
- protected destroy() {
- super.destroy();
- console.log('Extra cleaning can be done here');
- }
-}
-```
-
-## Load Balancing
-
-Client Engine automatically adjusts the number of instances based on the overall instance load.
-
-In Client Engine, there are two types of load balancing: the first is for requests initiated by clients through the REST API, and the second is for the load of the number of `Games` running on each instance. For requests initiated by clients via the REST API, the Client Engine automatically distributes the requests evenly among all current instances, without requiring any configuration work. For the second scenario, each `Game` object (per game) usually exists for a certain period of time, and in order to make the `Game` objects carried by each instance as balanced as possible, we need to additionally configure the `GameManager` into the load balancing system.
-
-This feature is implemented by the `LoadBalancerFactory` class provided by the SDK. As we can see in [GameManager Instantiation](#gamemanager-instantiation), `LoadBalancerFactory` generates a `LoadBalancer` object by binding `gameManager`, which is present in every Client Engine instance.
-
-When an instance of the Client Engine receives a REST API request from a client and calls a method in the `gameManager`, the load-balanced node `LoadBalancer` in the instance that receives the request finds the instance with the smallest number of `Games` in the cluster, forwards the specified `gameManager` API call to the `gameManager` of that instance to run, and returns the result. In this case, the `LoadBalancer` is only responsible for forwarding the request and does not care about how the request is handled.
-
-## API Documentation
-
-You can find more descriptions of the SDK's classes, methods, and properties in the API documentation. [Click to view Client Engine SDK API documentation](https://leancloud.github.io/client-engine-nodejs-sdk/).
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/quick-start-node.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/quick-start-node.mdx
deleted file mode 100644
index 906251edb..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/client-engine/quick-start-node.mdx
+++ /dev/null
@@ -1,102 +0,0 @@
----
-title: Client Engine Quick Start - Node.js
-sidebar_label: Quick Start
-sidebar_position: 2
----
-
-This document helps you get up to speed on how to create a Client Engine project, a simple two-player rock-paper-scissors game that relies on the Multiplayer JavaScript SDK for its logic.
-
-In this document, we'll first learn how to start the project locally, briefly try it out, and then deploy the project to the cloud. After that, we will introduce the detailed game logic and how to develop our own game in [Developer Guide](/sdk/multiplayer/client-engine/guide-node/).
-
-### Start the project
-
-### Install command line tools
-
-Please check the **[Installation section](/sdk/engine/cli/#installation)** of the documentation of the command line tool to install the command line tool and execute the **[Login](/sdk/engine/cli/#login-account)** command to log in.
-
-### Create a project
-
-Get the sample project from Github. Please use this project as your project base:
-
-```js
-git clone https://github.com/leancloud/client-engine-nodejs-getting-started
-cd client-engine-nodejs-getting-started
-```
-
-Add the application's App ID and other information to the project:
-
-```sh
-lean switch
-```
-
-In the first step for selecting an App, select the LeanCloud app that corresponds to your game. In the second step, when selecting the Cloud Engine group, you must select the `_client-engine` group, for which LeanCloud provides optimized maintenance and support specifically for the Client Engine, as shown here:
-
-![image](/img/client-engine/lean-switch.png)
-
-### Running locally
-
-First install the necessary dependencies in the current project's directory by executing the following command:
-
-```sh
-npm install
-```
-
-If the Data Storage service will also be used, execute the following command:
-
-```sh
-npm install leancloud-storage
-```
-
-Open the debug log when you start the application:
-
-```sh
-DEBUG=ClientEngine*,RPS*,Play lean up
-```
-
-If you do not need the debug log, you can start it directly with the following command:
-
-```sh
-lean up
-```
-
-After startup, open `http://localhost:3000/` in your browser to check if the project started properly.
-
-### Visit the site
-
-#### Experience the game
-
-After the server-side project is started, if you want to experience the Demo game, you need to open two additional [Client Sample Demo](https://client-engine-app.leanapp.cn/) pages at the same time, and do the following configurations in these two pages:
-
-Click Configs and enter the App ID, App Key, and server URL of the previous selected application into APP_ID, APP_KEY, and PLAY_SERVER:
-
-```sh
-# If your browser is already logged into LeanCloud, select the relevant app below and copy and paste the relevant information into Configs:
- APP_ID: "your-app-id"
- APP_KEY: "your-app-key"
-```
-
-Next, enter `http://localhost:3000` in Client Engine Server. As shown in the figure:
-
-![image](/img/client-engine/browser-demo.png)
-
-Once the information is filled in, click Login to Play to start the game.
-
-#### Client Code
-
-If you want to see the detailed client code, you can visit [Client Sample Code](https://github.com/leancloud/client-engine-demo-webapp) on github.
-
-## Deploying to the Cloud
-
-To deploy to the staging environment, run the following command in the root directory:
-
-```sh
-lean deploy --staging
-```
-
-Log in to the LeanCloud console in a browser, bind a [Cloud Engine domain](/sdk/domain/guide/#cloud-engine-domain) (custom domains starting with `stg-` will be automatically bound to the staging environment), and then visit the corresponding URL to see the text indicating that the Client Engine server is running.
-
-For other detailed deployment methods, please refer to [Deployment](/sdk/engine/cli/#deployment) in the command line tools documentation.
-
-## Your first Client Engine game
-
-Next, please see the documentation [Your First Client Engine Game](/sdk/multiplayer/client-engine/first-game-node/) for a step-by-step guide on how to develop a rock-paper-scissors game based on this initial project.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/error-code.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/error-code.mdx
deleted file mode 100644
index fb2673e18..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/error-code.mdx
+++ /dev/null
@@ -1,300 +0,0 @@
----
-title: Multiplayer Error Codes
-sidebar_label: Error Codes
-sidebar_position: 5
----
-
-## Application-level error code
-
-### 4001
-
-- Message - APP_NOT_FOUND
-- Meaning - App not found. Please make sure the App ID and App Key are entered correctly and the correct node is set.
-
-### 4002
-
-- Message - INSUFFICIENT_BALANCE
-- Meaning - The account to which the application belongs has negative balance. Please recharge the account before using the application.
-
-
-
-### 4004
-
-- Message - CCU_QUOTA_EXCEEDED
-- Meaning - CCU has exceeded the current quota limit. Please upgrade to Business or Enterprise Edition.
-
-
-
-### 4006
-
-- Message - JOIN_OR_CREATE_ROOM_NOT_ALLOWED_DUE_TO_APP_MSG_QUOTA_EXCEEDED
-- Meaning - The maximum message sending rate in an application room exceeds 500 messages per second, prohibiting the creation of new rooms and joining other rooms.
-
-## Service Fault Error Code
-
-### 4200
-
-- Message - INTERNAL_ERROR
-- Meaning - Internal server error. Contact technical support.
-
-### 4202
-
-- Message - SERVICE_TEMPORARILY_UNAVAILABLE
-- Meaning - Game service is temporarily unavailable. Please contact technical support.
-
-## Room error code
-
-### 4301
-
-- Message - ROOM_NOT_FOUND
-- Meaning - Room could not be found. The following conditions will cause this error:
- - No room matches the criteria during random matching. You can create a new room at this point.
- - Room name was specified but the Room has been destroyed or the name is incorrect.
-
-### 4302
-
-- Message - ROOM_FULL
-- Meaning - Room is full. Please join another room.
-
-
-
-### 4308
-
-- Message - ROOM_MEMBERSHIP_REQUIRED
-- Meaning - Requires the current player to be in the room in order to perform the action.
-
-### 4309
-
-- Message - ROOM_GAME_VERSION_OR_SDK_VERSION_NOT_MATCH
-- Meaning - The Game Version or SDK Version of the user who created the Room does not match the Game Version or SDK Version of the user who is currently in the Room. This error occurs when you force join a room with a Room name that does not match your Game Version or SDK Version.
-
-
-
-### 4311
-
-- Message - ROOM_ALREADY_EXISTS
-- Meaning - The Room already exists. Create a room with another Room name.
-
-### 4312
-
-- Message - ROOM_ATTRS_FULL
-- Meaning - The attributes of the Room have reached the maximum length limit.
-
-### 4313
-
-- Message - INVALID_ROOM_ATTR
-- Meaning - The attribute of Room does not match the requirement.
-
-### 4314
-
-- Message - ROOM_CLOSED
-- Meaning - Room is closed. This error occurs when actively joining a room that is already closed.
-
-### 4315
-
-- Message - TARGET_MASTER_CLIENT_OFFLINE
-- Meaning - The target Master was not online when the Master was transferred.
-
-### 4316
-
-- Message - INVALID_ROOM_ID
-- Meaning - Room id is not in the required format.
-
-### 4317
-
-- Message - SHOULD_LEAVE
-- Meaning - The user joins another room without leaving the current room. Please call the leave method first before joining another room.
-
-
-
-
-
-
-
-
-
-
-
-### 4324
-
-- Message - SHOULD_JOIN
-- Meaning - The Client is not currently in any Room, but is trying to do some operation in a Room. Please join a Room first before doing the operation.
-
-
-
-### 4326
-
-- Message - ROOM_ATTR_NOT_MATCHED
-- Meaning - When you join a Room by matching Room attributes, there are no rooms matching the relevant attributes, so you can create a new room matching the relevant attributes.
-
-
-
-### 4328
-
-- Message - OPERATION_NOT_ALLOWED
-- Meaning - Not authorised to do this operation.
-
-### 4329
-
-- Message - PLAYER_PROPERTIES_FULL
-- Meaning - User properties are full. Please reduce the size of the properties.
-
-
-
-## RPC message related errors
-
-
-
-### 4406
-
-- Message - NO_VALID_MESSAGE_RECEIVER
-- Meaning - The message has no legitimate recipient.
-
-
-
-## Other errors
-
-### 4101
-
-- Message - DUPLICATE_LOGIN
-- Meaning - On a connection that already has a user logged in, another login request is received from another user.
-
-### 4102
-
-- Message - DUPLICATE_CONNECTIONS
-- Meaning - The same user is logged in on different connections.
-
-### 4013
-
-- Message - SIGNATURE_VERIFICATION_FAILED
-- Meaning - Signature error.
-
-### 4104
-
-- Message - INVALID_APP_ID_OR_CLIENT_ID
-- Meaning - App id or Client id format is not legal.
-
-### 4105
-
-- Message - SESSION_REQUIRED
-- Meaning - The user sent a request without logging in.
-
-
-
-
-
-
-
-### 4110
-
-- Message - FRAME_TOO_LONG
-- Meaning - The received packet was too large and exceeded the limit.
-
-
-
-
-
-### 4121
-
-- Message - INVALID_PARAMS
-- Meaning - Wrong parameter. Please check the detail for more information.
-
-
-
-
-
-
-
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/features.mdx
deleted file mode 100644
index 30113110c..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/features.mdx
+++ /dev/null
@@ -1,815 +0,0 @@
----
-title: Multiplayer Introduction
-sidebar_label: Introduction
-sidebar_position: 1
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import { Conditional } from "/src/docComponents/conditional";
-
-Multiplayer is a backend service specifically designed for multiplayer online games by TDSLeanCloud. Developers don't need to build their own backend systems; they can easily implement features such as player matching and real-time battle message synchronization using cloud services.
-
-## Core Features
-
-- **Player Matching**: Match players together to play games either randomly or based on specified criteria. In online battles, the matching functionality brings players about to play together into the same room (Room). For example, in games like "Identity V," "Honor of Kings," and "PUBG," players can quickly match with others by clicking "Quick Match," and they all enter the same room to prepare for the game. Players can also create rooms and invite friends to play together.
-- **Fast Battle Message Synchronization**: Real-time bidirectional communication between the client and server is achieved through WebSocket channels, ensuring that all in-game messages are quickly synchronized.
-- **Game Logic Processing**: Multiplayer provides [MasterClient](/sdk/multiplayer/guide/js/#masterclient) as the client host for controlling game logic. All in-game logic is managed by the MasterClient, and in case the MasterClient unexpectedly disconnects, the client with the best network status will be automatically switched to MasterClient to ensure smooth gameplay. Developers can also choose to implement game logic on the server side (server-side game logic support is under development).
-- **Multi-Platform Support**: Perfectly compatible with Unity and Cocos Creator, supporting multiple platforms.
-
-## Features
-
-- Supports dynamic scalability to handle massive concurrency with ease.
-- Built on a proven underlying architecture, it has undergone deep optimization and improvements, ensuring stability for handling message delivery rates of up to billions per second.
-
-## Core Concepts
-
-### Client and UserId
-
-In the Multiplayer service, each terminal is referred to as a "Client." Each Client has a unique identifier called `UserId` throughout the entire Multiplayer service. This `UserId` can only consist of English letters, numbers, and underscores, with a length of up to 32 characters and shall be unique within the application. Each game player is definitely a Client, but not all Clients are actual players. For instance, the MasterClient that manages rooms in the Client Engine or self-made AI players are also Clients.
-
-The Multiplayer service only allows one Client to establish a connection with one server at a time. If a Client with an already logged-in `UserId` attempts to log in again, the second login will kick out the previous one.
-
-### Rooms and ActorId
-
-Once players are successfully matched, they will enter the same room to play the game, and battle messages will be swiftly synchronized within this room. Each player in this room has a unique ActorId, which is used for all communication within the room. When a player leaves the room, the ActorId becomes invalid. Upon entering a new room, the player will be assigned a new ActorId specific to that room.
-
-**A room can support a maximum of 10 players online simultaneously.**
-
-## Game Core Process
-
-Here's a simple example code to help you quickly understand the overall process. For detailed development guidance, please refer to:
-
-- [Multiplayer Development Guide · JavaScript](/sdk/multiplayer/guide/js/)
-- [Multiplayer Development Guide · C#](/sdk/multiplayer/guide/cs/)
-
-### Connecting to the Server
-
-
-<>
-
-
-
-```js
-const client = new Client({
- // Set your APP ID
- appId: {{appid}},
- // Set your APP Key
- appKey: {{appkey}},
- // Set the Server (replace xxx.example.com with your custom API domain bound to your app)
- playServer: 'https://xxx.example.com',
- // Set the user id
- userId: 'tarara',
- // Set the game version; optional; default is 0.0.1; players with different versions won't match in the same room
- gameVersion: '0.0.1'
-});
-
-client.connect().then(()=> {
- // Connection successful
-}).catch(console.error);
-```
-
-
-
-
-
-```js
-const client = new Client({
- appId: 'your-client-id', // Your game's Client ID
- appKey: 'your-client-token', // Your game's Client Token
- playServer: 'https://your_server_url', // Your game's API domain
- userId: 'tarara', // Set user id
- gameVersion: '0.0.1' // Set game version; optional; default is 0.0.1; players with different versions won't match in the same room
-});
-
-client.connect().then(()=> {
- // Connection successful
-}).catch(console.error);
-```
-
-- You can find your game's `Client ID` and `Client Token` in **Developer Center > Your Game > Game Services > App Configuration**.
-- The API domain can be found in **App Configuration > Domain Configuration > API**. Refer to the documentation on [Domains](/sdk/domain/guide/) for more information.
-
-
-
->
-<>
-
-```cs
-Play.UserID = "tarara";
-// You can declare the game version when connecting to the server; players with different versions won't match in the same room
-Play.Connect("0.0.1");
-```
-
->
-
-
-### Player Matching
-
-#### Random Matching
-
-The most common scenario for single players is to quickly match with other players. The implementation steps are as follows:
-
-1、Call `JoinRandomRoom` to start matching.
-
-
-<>
-
-```js
-client
- .joinRandomRoom()
- .then(() => {
- // Successfully joined the room
- })
- .catch(console.error);
-```
-
->
-<>
-
-```cs
-Play.JoinRandomRoom();
-```
-
->
-
-
-2、In ideal cases, players will enter a room with available slots and start the game.
-
-
-<>
-
-```js
-// Use the Promise returned by joinRandomRoom to determine if joining the room was successful in the JavaScript SDK
-```
-
->
-<>
-
-```cs
-play.On(Event.ROOM_JOINED, (evtData) => {
- // Successfully joined the room
-
-});
-```
-
->
-
-
-3、If there is no empty room, it will fail to join. At this point we can create a room in the callback triggered by the failure and wait for others to join. When creating the room:
-
-- You don't need to worry about the name of the room.
-- The default maximum number of people in a room is 10. You can set MaxPlayerCount to limit the maximum number of people.
-- Set [Retention time after player dropped](/sdk/multiplayer/guide/cs/#retain-disconnected-user-in-room). If the player is added back to the room within the valid time, the room will still retain the player's custom attributes.
-
-
-<>
-
-```js
-client
- .joinRandomRoom()
- .then()
- .catch((error) => {
- if (error.code === 4301) {
- const options = {
- // Set the maximum number of people so that when the room is full, the server will not match new players in.
- maxPlayerCount: 4,
- // Set the hold time after a player is dropped to 120 seconds
- playerTtl: 120,
- };
- // Creating a Room
- client
- .createRoom({
- roomOptions: options,
- })
- .then(() => {
- // Room created successfully
- });
- }
- });
-```
-
->
-<>
-
-```cs
-// This callback is triggered when joining fails
-play.On(Event.ROOM_JOIN_FAILED, (evtData) =>
-{
- var options = new RoomOptions()
- {
- // Set the maximum number of players so that when the room is full, the server won't match new players in.
- MaxPlayerCount = 4,
- // Set the hold time after a player is dropped to 120 seconds.
- PlayerTtl = 120,
- };
- play.CreateRoom(roomOptions: options);
-});
-```
-
->
-
-
-#### Customised room matching rules
-
-There are times when we want to match players with similar levels. For example, if the current player is level 5, he can only be matched with players of level 0-10, and players above level 10 cannot be matched. This scenario can be realised by setting attributes for the room. The logic is as follows:
-
-1. Determine the matching attributes, e.g. level 0-10 is level-1, and levels above 10 is level-2.
-
-
-<>
-
-```js
-var matchLevel = 0;
-if (level < 10) {
- matchLevel = 1;
-} else {
- matchLevel = 2;
-}
-```
-
->
-<>
-
-```cs
-int matchLevel = 0;
-if (level < 10) {
- matchLevel = 1;
-} else {
- matchLevel = 2;
-}
-```
-
->
-
-
-2、Join the room according to the matching attributes
-
-
-<>
-
-```js
-const matchProps = {
- level: matchLevel,
-};
-
-client
- .joinRandomRoom({ matchProperties: matchProps })
- .then(() => {
- // Successfully joined the room
- })
- .catch(console.error);
-```
-
->
-<>
-
-```cs
-Hashtable matchProp = new Hashtable();
-matchProp.Add("matchLevel", matchLevel);
-Play.JoinRandomRoom(matchProp);
-```
-
->
-
-
-3. If randomly joining a room fails, a room with matching attributes is created to wait for other people of the same level to join.
-
-
-<>
-
-```js
-const matchProps = {
- level: matchLevel,
-};
-
-client
- .joinRandomRoom({ matchProperties: matchProps })
- .then()
- .catch((error) => {
- if (error.code === 4301) {
- const options = {
- // Set the maximum number of people so that when the room is full, the server won't match new players in.
- maxPlayerCount: 4,
- // Set the hold time after a player is dropped to 120 seconds
- playerTtl: 120,
- // Custom properties of the room
- customRoomProperties: matchProps,
- // Select the key for matching from the room's custom properties
- customRoomPropertyKeysForLobby: ["level"],
- };
-
- client
- .createRoom({
- roomOptions: options,
- })
- .then()
- .catch(console.error);
- }
- });
-```
-
->
-<>
-
-```cs
-play.On(Event.ROOM_JOIN_FAILED, (error) => {
- if (error["code"] == 4301)
- {
- var props = new Dictionary();
- props.Add("level", 2);
- var options = new RoomOptions()
- {
- // Set the maximum number of people so that when the room is full, the server won't match new players in.
- MaxPlayerCount = 3,
- // Set the hold time after a player is dropped to 120 seconds
- PlayerTtl = 120,
- // Custom properties of the room
- CustomRoomProperties = props,
- // Select the key for matching from the room's custom properties
- CustoRoomPropertyKeysForLobby = new List() { "level" },
- };
- play.CreateRoom(roomOptions: options);
- }
-});
-```
-
->
-
-
-#### Playing with friends
-
-Suppose PlayerA wants to play the game with his best friend PlayerB, then there are two scenarios:
-
-- Just two people playing together, no strangers allowed.
-- Friends and strangers playing together
-
-##### No strangers allowed
-
-1. PlayerA creates a room and makes it invisible, so that other people don't randomly join the room that PlayerA has created.
-
-
-<>
-
-```js
-const options = {
-// Room invisible
- visible: false,
-};
-client
- .createRoom({
- roomOptions: options,
- })
- .then()
- .catch(console.error);
-```
-
->
-<>
-
-```cs
-var options = new RoomOptions()
-{
- Visible = false,
-};
-play.CreateRoom(roomOptions: options);
-```
-
->
-
-
-2. PlayerA tells PlayerB the name of the room through some kind of communication (e.g. [instant messaging](/sdk/im/features/)).
-
-3. PlayerB joins the room based on the room name.
-
-
-
-<>
-
-```js
-client.joinRoom("LiLeiRoom").then().catch(console.error);
-```
-
->
-<>
-
-```cs
-Play.JoinRoom(roomName);
-```
-
->
-
-
-##### Friends and strangers playing together
-
-PlayerA invites PlayerB via some communication method (e.g. [Instant Messaging](/sdk/im/features/)), and PlayerB accepts the invitation.
-
-1、PlayerA sets up a match with PlayerB to enter a room.
-
-
-<>
-
-```js
-client
- .joinRandomRoom({ expectedUserIds: ["playerB"] })
- .then(() => {
- // Join Success
- })
- .catch(console.error);
-```
-
->
-<>
-
-```cs
-Play.JoinRandomRoom(expectedUserIds: new string[] {"playerB"});
-```
-
->
-
-
-2、If there is enough space in the room, PlayerA joins successfully.
-
-
-<>
-
-```js
-// JavaScript SDK determines whether a room has been successfully joined by using the joinRandomRoom Promise.
-```
-
->
-<>
-
-```cs
-play.On(Event.ROOM_JOINED, (evtData) => {
- // TODO can do things like jumping to other scenes
-
-});
-```
-
->
-
-
-PlayerA tells PlayerB the roomName of the room it has joined via some form of communication (e.g. [instant messaging](/sdk/im/features/)), and PlayerB joins the room based on the roomName.
-
-
-<>
-
-```js
-client.joinRoom("LiLeiRoom").then().catch(console.error);
-```
-
->
-<>
-
-```cs
-Play.JoinRoom(roomName);
-```
-
->
-
-
-3. If there is no suitable room then create and join the room:
-
-
-<>
-
-```js
-const expectedUserIds = ["playerB"];
-client
- .joinRandomRoom({ expectedUserIds })
- .then()
- .catch((error) => {
- // No room available or insufficient room space
- if (error.code === 4301 || error.code === 4302) {
- client
- .createRoom({
- expectedUserIds: expectedUserIds,
- })
- .then()
- .catch(console.error);
- }
- });
-```
-
->
-<>
-
-```cs
-play.On(Event.ROOM_JOIN_FAILED, (error) => {
- var expectedUserIds = new List() { "cr3_2" };
- Play.CreateRoom(expectedUserIds: expectedUserIds);
-});
-```
-
->
-
-
-After PlayerA creates a room, it tells PlayerB the roomName of the room it has joined via some form of communication (e.g., [instant messaging](/sdk/im/features/)), and PlayerB joins the room based on the roomName.
-
-
-<>
-
-```js
-client.joinRoom("LiLeiRoom").then().catch(console.error);
-```
-
->
-<>
-
-```cs
-Play.JoinRoom(roomName);
-```
-
->
-
-
-For other matching interfaces, see the room matching documentation: [JavaScript](/sdk/multiplayer/guide/js/#room-matching), [C#](/sdk/multiplayer/guide/cs/#room-matching).
-
-### In game
-
-#### Relevant concepts
-
-- **MasterClient**: Multiplayer uses the [MasterClient](/sdk/multiplayer/guide/js/#masterclient) to act as a computational host on the client side, where the MasterClient controls the game logic, such as deciding whether to start or end the game, who should play the next round, how many coins should be deducted from the player, etc.
-- **Custom Attributes**: Custom attributes are divided into [Room Custom Attributes](/sdk/multiplayer/guide/js/#room-custom-attributes) and [Player Custom Attributes](/sdk/multiplayer/guide/js/#player-custom-attributes). We recommend adding game data to the custom attributes, such as the current room map, total coin bet, everyone's cards, etc. This way, when the MasterClient is transferred, the new MasterClient can get the latest data from the current game and continue to calculate.
-
-#### Starting the game
-
-Before the game starts, it is recommended that each player has a ready status. When all players are ready, the MasterClient will start the game. The room must be made invisible before the game starts to prevent other players from being matched during the game.
-
-Player A sets the ready state by setting a custom property:
-
-
-<>
-
-```js
-// Player setup readiness
-const props = {
- ready: true,
-};
-// Request to set player attributes
-play.player
- .setCustomProperties(props)
- .then(() => {
- // Setting properties succeeded
- })
- .catch(console.error);
-```
-
->
-<>
-
-```cs
-// Player setup readiness
-Hashtable prop = new Hashtable();
-prop.Add("ready", true);
-play.Player.SetCustomProperties(props);
-```
-
->
-
-
-All players (including PlayerA) are notified of the event callback:
-
-
-<>
-
-```js
-play.on(Event.PLAYER_CUSTOM_PROPERTIES_CHANGED, (data) => {
- // Only the MasterClient performs this operation.
- if (play.player.isMaster) {
- // Check the number of players who are ready in a method you defined; you can get the list of players via play.room.playerList.
- const readyPlayerCount = getReadyPlayerCount();
- // Start the game if players are all set
- if (
- readyPlayersCount > 1 &&
- readyPlayersCount == play.room.playerList.length()
- ) {
- // Set the room to be invisible to avoid other players being matched in
- play.setRoomVisible(false);
- // Start the game
- start();
- }
- }
-});
-```
-
->
-<>
-
-```cs
-
-play.On(Event.PLAYER_CUSTOM_PROPERTIES_CHANGED, (evtData) => {
- // Only the MasterClient performs this operation.
- if (play.Player.IsMaster)
- {
- // Check the number of players who are ready in your own method; you can get the player list via play.Room.playerList.
- var readyPlayerCount = getReadyPlayerCount();
- // Start the game if players are all set
- if (readyPlayersCount > 1 && readyPlayersCount == Play.Players.Count())
- {
- // Set the room to be invisible to avoid other players being matched in
- play.SetRoomVisible(false);
- // Start the game
- start();
- }
- }
-});
-```
-
->
-
-
-#### Sending messages in game
-
-Most messages in the game are sent to the [MasterClient](/sdk/multiplayer/guide/js/#masterclient), where the MasterClient crunches the numbers and decides what to do next. Suppose we have this scenario: Player A tells the MasterClient that he is done following cards, then the MasterClient receives the message and informs everyone that the next player to act is Player B.
-
-The process of sending the message is as follows:
-
-1、Player A sends a custom event `follow` to notify MasterClient that his follow is complete.
-
-
-<>
-
-```js
-// Set the receiving group of the event to Master
-const options = {
- receiverGroup: ReceiverGroup.MasterClient,
-};
-
-// Set the message to be sent
-const eventData = {
- actorId: play.player.actorId,
-};
-
-// Set the Event Id
-const FOLLOW_EVENT_ID = 1;
-
-// Send event
-play.sendEvent(FOLLOW_EVENT_ID, eventData, options);
-```
-
->
-<>
-
-```cs
-// Set the receiving group of the event to Master
-var options = new SendEventOptions() {
- ReceiverGroup = ReceiverGroup.MasterClient
-};
-// Set the message to be sent
-var eventData = new Dictionary();
-eventData.Add("actorId", play.player.actorId);
-
-// Set the Event Id
-byte followEventId = 1;
-
-// Send event
-play.SendEvent(followEventId, eventData, options);
-```
-
->
-
-
-2、The relevant method in the MasterClient is triggered. The MasterClient calculates that the next player to act is PlayerB, and then calls the `next` method to notify all players that PlayerB currently needs to act.
-
-
-<>
-
-```js
-// Event.CUSTOM_EVENT method will be triggered
-play.on(Event.CUSTOM_EVENT, event => {
- const { eventId } = event;
-
- if (eventId === FOLLOW_EVENT_ID) {
- // follow custom events
- // Determine that PlayerB will act next
- int PlayerBId = getNextPlayerId();
-
- // Notify all players that PlayerB will act next
- const options = {
- receiverGroup: ReceiverGroup.All,
- };
- const eventData = {
- actorId: PlayerBId,
- };
- const NEXT_EVENT_ID = 2;
- play.sendEvent(NEXT_EVENT_ID, eventData, options);
- }
-});
-
-```
-
->
-<>
-
-```cs
-// Event.CUSTOM_EVENT method will be triggered
-play.On(Event.CUSTOM_EVENT, (evtData) => {
- // Getting event parameters
- var eventId = evtData["eventId"];
- if (eventId == followEventId) {
- byte nextEventId = 2;
-
- // Content of the event
- var eventData = new Dictionary();
- eventData.Add("actorId", PlayerBId);
-
- // Send to all
- var options = new SendEventOptions()
- {
- ReceiverGroup = ReceiverGroup.All
- };
-
- play.SendEvent(nextEventId, eventData, options);
- }
-});
-```
-
->
-
-
-3、Relevant methods are triggered for all players.
-
-
-<>
-
-```js
-// Event.CUSTOM_EVENT method will be triggered
-play.on(Event.CUSTOM_EVENT, event => {
- const { eventId, eventData } = event;
- if (eventId === FOLLOW_EVENT_ID) {
- ......
- };
- if (eventId === NEXT_EVENT_ID) {
- // next event logic
- console.log('Next Player:' + eventData.actorId);
- }
-});
-
-```
-
->
-<>
-
-```cs
-play.On(Event.CUSTOM_EVENT, (evtData) => {
- if (eventId == followEventId)
- {
- ......
- }
-
- if (eventId == nextEventId)
- {
- // next event logic
- var actorId = evtData["actorId"];
- }
-});
-```
-
->
-
-
-For more detailed usage and introduction, please refer to :
-
-- [JavaScript - Custom Events](/sdk/multiplayer/guide/js/#customevents)
-- [C# - Custom Events](/sdk/multiplayer/guide/cs/#customevents)
-
-#### Disconnect and reconnect in game
-
-If the MasterClient is located on the client side, when the MasterClient is disconnected, the Multiplayer service will pick another member to be the new MasterClient, and the original MasterClient will become a normal member after returning to the room. Please refer to [Disconnect Reconnect](/sdk/multiplayer/guide/js/#disconnect-reconnect) for details.
-
-#### Exiting the room
-
-
-<>
-
-```js
-play
- .leaveRoom()
- .then(() => {
- // Successfully exited the room
- })
- .catch(console.error);
-```
-
->
-<>
-
-```cs
-Play.LeaveRoom();
-```
-
->
-
-
-## Documentation
-
-### JavaScript
-
-- [Quickstart](/sdk/multiplayer/start/js/): Getting up to speed with Multiplayer and running a small demo.
-- Multiplayer Development Guide - JavaScript](/sdk/multiplayer/guide/js/): A detailed introduction to all the features and interfaces of Multiplayer.
-
-### C#
-
-- [Quickstart](/sdk/multiplayer/start/cs/): Getting started and running a small demo.
-- [Multiplayer Development Guide - C#](/sdk/multiplayer/guide/cs/): A detailed introduction to all the features and interfaces of Multiplayer.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/guide/cs.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/guide/cs.mdx
deleted file mode 100644
index ef31900cd..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/guide/cs.mdx
+++ /dev/null
@@ -1,893 +0,0 @@
----
-title: Multiplayer Development Guide - C#
-sidebar_label: C#
-sidebar_position: 2
----
-
-import { Conditional } from "/src/docComponents/conditional";
-
-## Introduction
-
-Multiplayer is a C#-based game SDK that provides a complete client-side SDK solution for online games with strong networking requirements, thus eliminating the need for development teams to build their own servers and saving most of the development and maintenance costs. The main features provided by Multiplayer are as follows:
-
-- Getting room list
-- Creating a room
-- Joining a room
-- Randomly joining a (eligible) room
-- Getting players in a room
-- Getting, setting, and synchronizing room properties
-- Getting, setting, and synchronizing player properties
-- Sending and receiving custom events
-- Leaving a room
-
-## SDK Import
-
-Please read [Installation Guide](/sdk/multiplayer/start/cs/#installation) to get the dll library files.
-
-## Initialisation
-
-Import the required namespace.
-
-```cs
-using LeanCloud.Play;
-```
-
-Next we need to instantiate a client object for the Multiplayer SDK.
-
-
-
-```cs
-var client = new Client("your-app-id", "your-app-key", userId, playServer: "https://xxx.example.com", gameVersion: "0.0.1");
-// Please replace xxx.example.com with the domain name of the custom API your app is bound to.
-```
-
-
-
-
-
-```cs
-var client = new Client(
- "your-client-id", // Client ID of the game
- "your-client-token", // Client Token for the game
- "tarara", // Set the user id
- playServer: "https://your_server_url", // The game's API domain name
- gameVersion: "0.0.1" // Set the game version; optional; default is 0.0.1; players with different versions will not be matched in the same room
-);
-```
-
-- The `Client ID` and `Client Token` of the game can be viewed at **Developer Centre > Your Games > Game Services > Application Configuration**.
-- The API domain name is viewable at **Application Configuration > Domain Configuration > API**. Refer to the documentation for [domain name](/sdk/domain/guide/) for more information.
-
-
-
-Here
-`userId` serves as a unique identifier for the client connecting to the server.
-Note that this `userId` has the following restrictions:
-
-- Only letters, numbers, and underscores are allowed
-- Cannot exceed 32 characters in length
-- Globally unique within an application
-
-`gameVersion` indicates the version number of the client, which can be used to route to different game servers if multiple versions of the game are allowed to co-exist.
-
-## Connections
-
-### Establishing a connection
-
-Connect the current player to the Multiplayer service with the following code:
-
-```cs
-try {
- await client.Connect();
- // connection successful
-} catch (PlayException e) {
- // connection failure
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-## Lobby
-
-We recommend that you **don't add players to the lobby**, because in the lobby, the server will constantly send out the latest full list of rooms, and players will choose one of the rooms to play by themselves, which is not only unfriendly to the player experience, but also brings a lot of bandwidth pressure.
-We recommend you to **use [Room Matching](#room-matching) like most of the mobile games nowadays to quickly match players and let them start the game**.
-
-If you have a special game scenario where you need to get the room list, you can call the following method:
-
-```cs
-try {
- await client.JoinLobby();
- // Joined the lobby successfully
-} catch (PlayException e) {
- // Failed to join the lobby
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-When a player joins the lobby, the server sends the list of rooms in the current lobby to the client, and the developer can view the list of rooms or join a room to join the game as needed.
-
-```cs
-client.OnLobbyRoomListUpdated += () => {
- var roomList = client.LobbyRoomList;
- // TODO can do the logic for displaying the list of rooms.
-};
-```
-
-For more on `LobbyRoom`, see [API documentation](https://leancloud.github.io/Play-SDK-CSharp/html/classLeanCloud_1_1Play_1_1LobbyRoom.htm).
-
-### Related events
-
-| Event | Parameter | Description |
-| ---------------------- | ---- | ---------------- |
-| OnLobbyRoomListUpdated | None | Lobby Room List Updated |
-
-## Room Matching
-
-A room is a unit that generates "combat interactions" between players. For example, the card table of Landlord, the copy of MMO, the battle of WeChat game, etc., all belong to the category of room in a broad sense.
-The combat interactions between players are all done in the room. Therefore, how players enter a room is the key to room matching. Below we will analyse the commonly used "Room Matching" function from the aspects of "Create Room" and "Join Room".
-
-### Creating a Room
-
-We can create a room simply like this. The player who creates the room is the room owner ([MasterClient](#masterclient)), and when the owner creates a room successfully, he/she also joins the room.
-
-```cs
-try {
- await client.CreateRoom();
- // Successful room creation also means that you have successfully joined the room.
-} catch (PlayException e) {
- // Failed to create room
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-We can also create a room that sets the relevant information.
-
-```cs
-// Custom properties of the room
-var props = new PlayObject {
- { "title", "room title" },
- { "level", 2 }
-};
-var roomOptions = new RoomOptions {
- Visible = false,
- EmptyRoomTtl = 10000,
- PlayerTtl = 600,
- MaxPlayerCount = 2,
- CustomRoomProperties = props,
- CustoRoomPropertyKeysForLobby = new List { "level" },
- Flag = CreateRoomFlag.MasterSetMaster | CreateRoomFlag.MasterUpdateRoomProperties,
-};
-var expectedUserIds = new List { "cr3_2" };
-try {
- await client.CreateRoom(roomName, roomOptions, expectedUserIds);
- // Successful room creation also means that you have successfully joined the room.
-} catch (PlayException e) {
- // Failed to create room
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-The parameters `roomName`, `roomOptions` and `expectedUserIds` are optional.
-
-#### roomName
-
-The room name must be unique; if it is not set, the server generates a unique room id and returns it.
-
-#### roomOptions
-
-Specified parameters when creating a room, including:
-
-- `Opened`: whether the room is open or not. If set to false, no other players are allowed to join.
-- `Visible`: whether the room is visible. If set to false, it will not appear in the lobby's room list, but other players can join the room by specifying the room name.
-- `EmptyRoomTtl`: how long (in seconds) the room will be kept when there are no players in the room. Default is 0, i.e. the room data will be destroyed immediately when there are no players in the room. Maximum value is 300, i.e. 5 minutes.
-- `PlayerTtl`: the time (in seconds) to keep the player's data in the room when the player drops out. Default is 0, i.e. player data is destroyed immediately after the player drops out. Maximum value is 300, i.e. 5 minutes.
-- `MaxPlayerCount`: the maximum number of players allowed in the room.
-- `CustomRoomProperties`: custom properties for the room.
-- `CustomRoomPropertyKeysForLobby`: an array of keys in `CustomRoomProperties`. The properties contained in `CustomRoomPropertyKeysForLobby` will appear in the lobby's room Properties (`client.LobbyRoomList`), and the full set of properties can be viewed in `room.CustomProperties` after joining the room. These properties will be used when matching rooms.
-- `Flag`: flag bit for room creation. See [MasterClient drop without transfer](#masterclient-drop-transfer), [Specify other member as MasterClient](#specify-other-members-as-masterclient), and [Allow only MasterClient to modify room properties](#allow-only-masterclient-to-modify-room-properties) below for more details.
-
-#### expectedUserIds
-
-An array of specified player IDs. This parameter is mainly used to "reserve space" for certain players who can join the room.
-
-Note: These "specific players" will not actually join the room. There will only be space in the room reserved for those "specific players" to join. If you want to invite players to join a room, you need to send the room name to your friends through other channels, such as IM, WeChat, etc., and then your friends will join the room through the `JoinRoom(roomName)` interface.
-
-For more information about `CreateRoom`, please refer to [API Documentation](https://leancloud.github.io/Play-SDK-CSharp/html/classLeanCloud_1_1Play_1_1Client.htm#a0478f278b300dd4ae4c4e1fe311d3c7c).
-
-### Joining a room
-
-When a room is created, other players can participate in the game by "joining the room".
-
-#### Join the designated room
-
-You can join a room by specifying the room name.
-
-```cs
-try {
- // The player is joining the 'LiLeiRoom' room.
- await client.JoinRoom("LiLeiRoom");
- // Join the room successfully
-} catch (PlayException e) {
- // Failed to join room
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-When joining a room, you can also take up space for other players. If the remaining empty space in the room is less than the number of occupied spaces, you will fail to join the room.
-
-```cs
-var expectedUserIds = new List() { "LiLei", "Jim" };
-try {
- // Players are joining the "game" room and taking up space for LiLei and Jim
- await client.JoinRoom("game", expectedUserIds);
-} catch (PlayException e) {
- // Failed to join room
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-
-```
-
-For more on `JoinRoom`, see the [API documentation](https://leancloud.github.io/Play-SDK-CSharp/html/classLeanCloud_1_1Play_1_1Client.htm#ab19049bfb2cf5e62c746f5e4a4ce79b2).
-
-#### Randomly joining rooms
-
-Sometimes, instead of joining a specific room, we can randomly join "rooms that meet certain conditions" (or even no conditions), such as quick start, quick match, etc. In this case, we can randomly join rooms by calling the `JoinRandomRoom` method.
-
-```cs
-try {
- await client.JoinRandomRoom();
- // Join the room successfully
-} catch (PlayException e) {
- // Failed to join room
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-It is also possible to set "conditions" for random joining, such as randomly joining a room with level = 2.
-
-```cs
-// Setting Matching Properties
-var matchProps = new PlayObject {
- { "level", 2 }
-};
-try {
- await client.JoinRandomRoom(matchProps);
-} catch (PlayException e) {
- // Failed to join room
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-### Join or create a specific room
-
-Many games don't have scenarios that force a designated room owner. As long as a number of players can play in the same room, it is fine for anyone to be the owner. We can use `JoinOrCreateRoom()` to achieve this. This method will join the current player directly to the room if there is a relevant room, or create a new room if there isn't such a room.
-
-```cs
-try {
- // For example, if 4 players join a room with the name "room1" at the same time, if it doesn't exist, then create and join the room with the name "room1".
- await client.JoinOrCreateRoom("room1");
-} catch (PlayException e) {
- // Failed to join a room and did not successfully create a room
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-For more on `JoinOrCreateRoom`, see the [API documentation](https://leancloud.github.io/Play-SDK-CSharp/html/classLeanCloud_1_1Play_1_1Client.htm#ad4a489ac7663ee9eee812cba2659a187).
-
-### New player joining event
-
-For players who are already in the room, when a new player joins the room, the server will send the `OnPlayerRoomJoined` event to notify the client, and the client can use the properties of the new player to do some display logic.
-
-```cs
-// Register the event for new players joining
-client.OnPlayerRoomJoined += newPlayer => {
- // TODO New Player Joining Logic
-};
-```
-
-You can get all the players in the room by `client.Room.PlayerList`.
-
-### Determining local players
-
-Once you have all the players in the room, you can determine if a `Player` is the current local player.
-
-```cs
-var players = client.Room.PlayerList;
-var player = players[0];
-var isLocal = player.isLocal;
-```
-
-### Leaving a room
-
-The following interface can be called when the player wants to proactively leave the room.
-
-```cs
-try {
- await client.LeaveRoom();
- // Leaving the room successfully; you can do things like going to another scene
-} catch (PlayException e) {
- // Failed to leave the room
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-Other players in the room will receive the `OnPlayerRoomLeft` event.
-
-```cs
-// Register the event of a player leaving the room
-client.OnPlayerRoomLeft += leftPlayer => {
- // TODO can perform the destruction operation for the player's departure
-}
-```
-
-### Related events
-
-| Event | Parameter | Description |
-| ------------------ | ---------- | -------------- |
-| OnPlayerRoomJoined | newPlayer | A new player joined the room |
-| OnPlayerRoomLeft | leftPlayer | A player left the room |
-
-## MasterClient
-
-The room creator will become the MasterClient by default in the Multiplayer service. They can close the room, set the room to be invisible, kick people, and so on. Their device is also responsible for "logical operations". For example, it can be responsible for the following scenarios in the game:
-
-- Dealing cards for users in card games;
-- Controlling the timing and logic of killing monsters;
-- Judging the winner at the end of the game;
-- ...and so on.
-
-### MasterClient located on the player's client
-
-You can write the MasterClient logic in the client, and the player terminal ofthe room creator will take care of the game operations. In this case, Multiplayer provide the following convenient features for MasterClient:
-
-#### MasterClient Drop Transfer
-
-When a MasterClient is dropped, the Multiplayer service assigns a new player client as the MasterClient. Even if the original MasterClient comes back online, it does not become the new MasterClient. When the MasterClient is changed, the SDK dispatches the `OnMasterSwitched` event to notify the client when the MasterClient changes.
-
-```cs
-// Register the host switch event
-client.OnMasterSwitched += newMaster => {
- // TODO can display that the host is changed
-
- // You can determine whether to perform logical processing according to whether the current client is the Master.
- if (client.Player.IsMaster) {
-
- }
-}
-```
-
-#### Related Events
-
-| Event | Parameter | Description |
-| ---------------- | --------- | ----------- |
-| OnMasterSwitched | newMaster | Master changed |
-
-#### Specify other members as MasterClient
-
-During the game, if the MasterClient does not want to take the computing function anymore, it can call the following method to actively transfer its role to other player clients:
-
-```cs
-try {
- // Specify MasterClient by Player Id
- await client.SetMaster(newMasterId);
-} catch (PlayException e) {
- // Failed to set Master
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-After transferring the MasterClient identity, all players in the room will receive the `OnMasterSwitched` event.
-
-### MasterClient on the server
-
-In order to ensure game security and prevent users from cheating, we recommend that you host your MasterClient in the Client Engine provided by TDS and configure the following permissions:
-
-#### MasterClient will not be transferred when it drops
-
-After placing the MasterClient on the server, the MasterClient role should not be transferred even if it unexpectedly drops out of the game, in which case you need to specify the `FixedMaster` flag when creating a room:
-
-```cs
-var options = new RoomOptions {
- Flag = CreateRoomFlag.FixedMaster
-};
-await client.CreateRoom(roomOptions: options);
-```
-
-### MasterClient operation
-
-#### Set whether a room is open or not
-
-MasterClient can set whether a room is open or not, so that other players are not allowed to join when the room is closed.
-
-```cs
-try {
- // Setting the room to close
- await client.SetRoomOpen(false);
- Debug.Log(client.Room.Open);
-} catch (PlayException e) {
- // Failed to set
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-#### Setting whether a room is visible or not
-
-MasterClient can set whether a room is visible or not. When the room is not visible, this room will not appear in the player's lobby room list, but **other players can join by specifying roomName**.
-
-```cs
-try {
- // Setting the room invisible
- await client.SetRoomVisible(false);
- Debug.Log(client.Room.Visible);
-} catch (PlayException e) {
- // Failed to set
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-#### Kicking people
-
-MasterClient can kick other players in a room out of the room:
-
-```cs
-try {
- // You can pass in the code and msg of the kicker.
- await client.KickPlayer(otherPlayer.ActorId, 1, "You've been kicked out of your room.");
-} catch (PlayException e) {
- // Failed to kick
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-After being kicked out of a room, the kicked player receives the `OnRoomKicked` event.
-
-```cs
-client.OnRoomKicked += (code, msg) => {
- // code and msg are what the MasterClient passes when it kicks someone.
-};
-```
-
-At the same time, players other than the MasterClient that are still in the room will receive the `OnPlayerRoomLeft` event:
-
-```cs
-client.OnPlayerRoomLeft += leftPlayer => {
-
-};
-```
-
-## Custom Attributes and Synchronisation
-
-In order to meet the different gameplay needs of developers, the Multiplayer SDK allows developers to set custom properties.
-
-The main functions of custom property synchronization include:
-
-- Keeping the data consistent among multiple clients.
-- Custom attributes are managed by the server. When a player enters a room, all custom attributes will be available.
-
-Custom attributes are divided into "Room Custom Attributes" and "Player Custom Attributes".
-
-### Room Custom Attributes
-
-You can set a `PlayObject` type custom attribute for a room, such as the number of rounds in a battle, all the pieces in a room, and so on.
-
-```cs
-// Set the custom properties you want to modify
-var props = new PlayObject {
- { "gold", 1000 }
-};
-try {
- // Set the gold property to 1000
- await client.Room.SetCustomProperties(props);
- var newProperties = client.Room.CustomProperties;
-} catch (PlayException e) {
- // Failed to set the property
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-Note: This interface does not directly set the memory values of custom properties in the client, but sends a message to modify the custom properties, and the server makes the final decision whether to change them or not.
-
-When a room property is changed, the SDK will dispatch the `OnRoomCustomPropertiesChanged` event to notify all player clients (including itself).
-
-```cs
-// Registering room property change events
-client.OnRoomCustomPropertiesChanged += changedProps => {
- var props = client.Room.CustomProperties;
- var gold = props.GetInt("gold");
-};
-```
-
-Note: The `changedProps` parameter only indicates the currently changed parameters, not all properties. To get all properties, please get them via `client.Room.CustomProperties`.
-
-#### Allow only MasterClient to modify room properties
-
-By default, all clients in a room can modify the room's custom properties. If you want to allow only the MasterClient to do so, you can specify the `MasterUpdateRoomProperties` flag when creating the room:
-
-```cs
-var options = new RoomOptions {
- Flag = MasterUpdateRoomProperties
-};
-await client.CreateRoom(roomOptions: options);
-```
-
-### Player custom attributes
-
-Player custom attributes are essentially the same as [room custom attributes](#room-custom-attributes).
-
-```cs
-// Poker Objects
-var poker = new PlayObject {
- { "flower", 1 },
- { "num", 13 }
-};
-var props = new PlayObject {
- { "nickname", "Li Lei" },
- { "gold", 1000 },
- { "poker", poker }
-};
-try {
- // Request to set player attributes
- await client.Player.SetCustomProperties(props);
-} catch (PlayException e) {
- // Failed to set the property
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-Any player in the room (including yourself) who modifies their custom attributes triggers the Player Custom Attribute Change event:
-
-```cs
-// Register player custom attribute change events
-client.OnPlayerCustomPropertiesChanged += (player, changedProps) => {
- // Get all the player's custom attributes
- var props = player.CustomProperties;
- var title = props.GetString("title");
- var gold = props.GetInt("gold");
-};
-```
-
-### CAS
-
-CAS stands for Compare And Swap, which means check and update, and is used to avoid some concurrency problems.
-
-When `SetCustomProperties` is called, the server receives all the values submitted by the client, which is not enough for some scenarios.
-
-For example, a property holds the holder of an item in this room, i.e. the key of the property is the item and the value is the holder.
-Any client can set this property at any time, and if multiple clients call it at the same time, **the server will take the last call received as the final value**, which is usually illogical. Usually the item should belong to the first person who got it.
-
-`SetCustomProperties` has an optional parameter `expectedValues` that can be used as a judgement condition. The server will only update properties that currently match `expectedValues`. Updates with expired `expectedValues` will be ignored.
-
-Suppose there are 10 players in a room, but there is only 1 Tulong Knife, and the Tulong Knife can only be won by the first person who "grabs it".
-
-We can set the `tulong` value in `expectedValues`:
-
-```cs
-var props = new PlayObject {
- // X indicates the current client
- { "tulong", X }
-};
-var expectedValues = new PlayObject {
- // Current owner of the Tulong Knife
- { "tulong", null }
-};
-await client.Room.SetCustomProperties(props, expectedValues);
-```
-
-This way, after the first player gets the Tulong Knife, `tulong` corresponds to the value of first player, and subsequent requests with `expectedValues = { tulong: null }` will fail.
-
-### Related events
-
-| Events | Parameters | Description
-| ------------------------------- | ---------------------- | ------------------ |
-| OnRoomCustomPropertiesChanged | changedProps | Room custom property is changed |
-| OnPlayerCustomPropertiesChanged | (player, changedProps) | Player custom property is changed |
-
-## Custom events
-
-In [Custom Properties](#custom-attributes-and-synchronisation), we introduced how to customize the game's data structures and data types based on the game's requirements.
-However, data by itself is not enough. To allow different parties to communicate, custom events are also needed.
-
-### Sending custom events
-
-We can send various events through custom events, such as game start, card capture, release X skill, game end, etc.
-
-```cs
-var options = new SendEventOptions {
- // Set the receiving group of the event to Master
- ReceiverGroup = ReceiverGroup.MasterClient
- // You can also specify the receiver actorId
- // TargetActorIds = new List() { 1 },
-};
-// Setting Skill Id
-var eventData = new PlayObject {
- { "skillId", 123 }
-};
-// Setting the skill event id
-byte SKILL_EVENT_ID = 1;
-try {
- // Send custom events
- await client.SendEvent(SKILL_EVENT_ID, eventData, options);
-} catch (PlayException e) {
- // Failed to send the event
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-The `options` are the event sending parameters, including the receiving group and the receiver ID array.
-
-- ReceiverGroup is an enumeration of the targets of the received event, including Others (everyone in the room except yourself), All (everyone in the room), and MasterClient (the host).
-- The Receiver ID array is the enumerated value of the target of the received event, i.e., the player's `ActorId` array. The `ActorId` can be obtained via `player.ActorId`.
-
-Note: If both receiver group and receiver ID array are set, receiver ID array will override receiver group.
-
-### Receiving custom events
-
-When the event is sent successfully, the custom event `OnCustomEvent` in the event receiver will be triggered, and different events can be handled according to the `eventId` (event ID).
-
-```cs
-// Registering custom events
-client.OnCustomEvent += (eventId, eventData, senderId) => {
- if (eventId == SKILL_EVENT_ID) {
- // If it is a skill event
- var skillId = eventData.GetString("skillId");
- // TODO Handling the display of released skills
-
- }
-};
-```
-
-### `event` Parameters
-
-| parameter | type | description |
-| --------- | ---------- | ------------------------------- |
-| eventId | byte | Event Id for the event |
-| eventData | PlayObject | Event parameter |
-| senderId | int | Event sender ID (player's actorId) |
-
-## Disconnect
-
-In case of unstable network, you may be disconnected passively. When you are disconnected passively, the SDK will send `OnDisconnected` event to the client, where the developer can alert the player on the UI:
-
-```cs
-// Registering for disconnection events
-client.OnDisconnected += () => {
- // TODO Reconnect if needed
-};
-```
-
-## Disconnect and reconnect
-
-### Keep disconnected users in the room.
-
-Players may get disconnected when there's unstable network or when they put the game in the background. When the player drops out, the `OnPlayerActivityChanged` event will be triggered for other online players:
-
-```cs
-// Register Player Dropping/Connecting Events
-client.OnPlayerActivityChanged += player => {
- // Get the status of whether a user is "active" or not
- Debug.Log(player.IsActive);
- // TODO You can update display and run other logic according to the player's online status.
-};
-```
-
-If a player doesn't come back to the game for a long time, the server will remove the player from the room and destroy the player's data, and the other players in the room will receive the `OnPlayerRoomLeft` event.
-When creating a room, we can set the timeout via `PlayerTtl`, so that when a player drops out of the room, the server will keep the data of the dropped player in the room during the `PlayerTtl` time and wait for the player to come back online.
-
-```cs
-var options = new RoomOptions {
- // Set PlayerTtl to 300 seconds
- PlayerTtl = 300
-}
-await client.CreateRoom(roomOptions: options);
-```
-
-When a dropped player returns to the room within the `PlayerTtl` time, the other player's `OnPlayerActivityChanged` event will be triggered again, and the developer can determine the player's current state in this event.
-
-### Reconnect
-
-Users can reconnect to the server after being disconnected through the following interface.
-
-```cs
-client.OnDisconnected += async () => {
- // Reconnect
- await client.Reconnect();
-};
-```
-
-Note: This interface will only reconnect you to the server, not return you directly to the room if you were previously playing in the room.
-
-**Recommended** [Reconnect and get room](#rejoin-a-room), then the player can choose whether or not to return to the room.
-
-You can also just [Reconnect and return to room](#reconnect-and-return-to-room).
-
-### Reconnect and get room
-
-After reconnecting, the player can use the `FetchMyRoom` interface to fetch the room they were in before leaving:
-
-- If the player is still in the room, the room ID will be returned and the game will inform the player of this state. If the player chooses to return to the room they were in before leaving, they can call the `RejoinRoom` interface.
-- If the player is not in the room, or the room has been destroyed, a [4301](/sdk/multiplayer/error-code/#4301) exception will be returned, and the game can handle it accordingly.
-
-```cs
-string roomName = await client.FetchMyRoom();
-```
-
-### Rejoin a room
-
-When a player connects to the server, they can rejoin a room via the `Rejoin` interface. If the player calls this interface within the `PlayerTtl` time limit, the player will be able to rejoin the room successfully; if the `PlayerTtl` time limit is exceeded, rejoining the room will fail.
-
-```cs
-try {
- // reconnect
- await client.Reconnect();
- // The reconnection was successful. We're back in the same room as before.
- if (roomName) {
- try {
- await client.RejoinRoom(roomName);
- // If you return to the room successfully, the other players in the room will receive the `OnPlayerActivityChanged` event.
- } catch (PlayException e) {
- // Failed to return to room
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
- }
- }
-} catch (PlayException e) {
- // reconnect failure
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-### Reconnect and return to the room
-
-This interface is the equivalent of `Reconnect()` and `RejoinRoom()` combined. With this interface, you can directly reconnect and go back to the previous room.
-
-```cs
-try {
- await client.ReconnectAndRejoin();
- // Successfully return to the room; update data and interface
-} catch (PlayException e) {
- // Failure to reconnect or return
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-## Close
-
-Developers can also proactively close the SDK via the following interface, after which the Client needs to be re-instantiated if it needs to be used again.
-
-```cs
-client.Close();
-```
-
-## Error handling
-
-Specific exception messages can be caught with try-catch when we initiate a request. For example, when creating a room:
-
-```cs
-try {
- await client.createRoom();
- // Room Creation Successful
-} catch (PlayException e) {
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-## Critical error event
-
-If a major error is currently occurring, this event will be triggered. It won't be triggered easily, and if it is triggered, please contact technical support to deal with it.
-
-```cs
-client.OnError += (code, detail) => {
- // Contact Technical Support
-
-};
-```
-
-## Serialisation
-
-In the new version of Play, we provide richer ways to synchronise data. The main ones include container types (PlayObject/PlayArray) and custom types.
-
-### PlayObject
-
-`PlayObject` is a replacement for the `Dictionary` type in older versions.
-
-`PlayObject` implements the `IDictionary` interface, which provides a more convenient fetch interface in addition to the `IDictionary` interface.
-
-Common interfaces are listed below:
-
-```csharp
-// basic type
-public bool GetBool(object key);
-public int GetInt(object key);
-public float GetFloat(object key);
-...
-// Container type
-public PlayObject GetPlayObject(object key); // PlayObject supports nesting
-public PlayArray GetPlayArray(object key);
-public T Get(object key);
-```
-
-[For more interfaces, please refer to](https://leancloud.github.io/Play-SDK-CSharp/html/classLeanCloud_1_1Play_1_1PlayObject.htm)
-
-### PlayArray
-
-`PlayArray` implements the `IList` interface and is mainly used for synchronisation of array objects, similar to `PlayObject`.
-
-Commonly used interfaces are as follows:
-
-```csharp
-// basic type
-public bool GetBool(int index);
-public int GetInt(int index);
-public float GetFloat(int index);
-...
-// Container type
-public PlayObject GetPlayObject(int index);
-public PlayArray GetPlayArray(int index);
-public T Get(int index);
-// conversion interface
-public List ToList();
-```
-
-[For more interfaces, please refer to](https://leancloud.github.io/Play-SDK-CSharp/html/classLeanCloud_1_1Play_1_1PlayArray.htm)
-
-### Custom types
-
-In addition to supporting the two container types mentioned above, `Play` also supports synchronising data of `Custom Types`.
-
-Suppose we have a Hero type with id, name, and hp defined as follows:
-
-```csharp
-class Hero {
- int id;
- string name;
- int hp;
-}
-```
-
-To synchronise data of type Hero, the following two steps are required:
-
-#### Implement serialisation / deserialisation methods
-
-The implementation of serialisation methods is up to the developer. You can use protobuf, thrift, etc. It is sufficient if the serialisation and deserialisation interfaces supported by `Play` are met.
-
-```csharp
-public delegate byte[] SerializeMethod(object obj);
-public delegate object DeserializeMethod(byte[] bytes);
-```
-
-The following is an example of the way to serialise a `PlayObject` provided by `Play`.
-
-```csharp
-// Serialisation methods
-public static byte[] Serialize(object obj) {
- Hero hero = obj as Hero;
- var playObject = new PlayObject {
- { "id", hero.id },
- { "name", hero.name },
- { "hp", hero.hp },
- };
- return CodecUtils.SerializePlayObject(playObject);
-}
-// deserialisation method
-public static object Deserialize(byte[] bytes) {
- var playObject = CodecUtils.DeserializePlayObject(bytes);
- Hero hero = new Hero {
- id = playObject.GetInt("id"),
- name = playObject.GetString("name"),
- hp = playObject.GetInt("hp"),
- };
- return hero;
-}
-```
-
-#### Registering Custom Types
-
-When implementing a serialisation method, remember to register the custom type before using it.
-
-```csharp
-CodecUtils.RegisterType(typeof(Hero), typeCode, Hero.Serialize, Hero.Deserialize);
-```
-
-where `typeCode` is a numeric code representing the custom type, which is used to determine the custom type during deserialisation.
-
-## API Documentation
-
-For more interfaces and details, please refer to [API Interfaces](https://leancloud.github.io/Play-SDK-CSharp/html/).
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/guide/js.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/guide/js.mdx
deleted file mode 100644
index e9644f06b..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/guide/js.mdx
+++ /dev/null
@@ -1,909 +0,0 @@
----
-title: Multiplayer Development Guide - JavaScript
-sidebar_label: JavaScript
-sidebar_position: 1
----
-
-import { Conditional } from "/src/docComponents/conditional";
-
-## Preface
-
-Multiplayer is a JavaScript game SDK that provides a complete client-side SDK solution for online games with strong networking requirements, thus eliminating the need for development teams to build their own servers and saving most of the development and maintenance costs. The main features provided by Multiplayer are as follows:
-
-- Getting room list
-- Creating a room
-- Joining a room
-- Randomly joining a (eligible) room
-- Getting players in a room
-- Getting, setting, and synchronizing room properties
-- Getting, setting, and synchronizing player properties
-- Sending and receiving custom events
-- Leaving a room
-
-## SDK Import
-
-Please read [Installation Guide](/sdk/multiplayer/start/js/#installation) to get the JS library files.
-
-## Initialisation
-
-First of all, you need to introduce common types and constants in the SDK.
-
-```javascript
-const {
- // SDK
- Client,
- // Play SDK Event Constants
- Event,
- // event receiver group
- ReceiverGroup,
- // Creating Room Signs
- CreateRoomFlag,
-} = Play;
-```
-
-Note: Cocos Creator can't load `Play` into global variables properly when building `WeChatGame` project, so you need to import `Play` module first.
-
-```javascript
-const Play = require("./play");
-```
-
-Next we need to instantiate a client object for the Multiplayer SDK.
-
-
-
-```javascript
-const client = new Client({
- // Setting the APP ID
- appId: "your-app-id",
- // Setting the APP Key
- appKey: "your-app-key",
- // Setting up the Server (replace xxx.example.com with the custom API domain name your app is bound to)
- playServer: 'https://xxx.example.com',
- // Setting the user id
- userId: 'tarara',
- // Set game version number (optional, default 0.0.1)
- gameVersion: '0.0.1'
-});
-```
-
-
-
-
-
-```js
-const client = new Client({
- appId: 'your-client-id', // the game's Client ID
- appKey: 'your-client-token', // the game's Client Token
- playServer: 'https://your_server_url', // the game's API domain name
- userId: 'tarara', // set user id
- gameVersion: '0.0.1' // Set the game version, optional, default 0.0.1, players with different versions will not be matched in the same room.
-});
-```
-
-- The `Client ID` and `Client Token` of the game can be viewed at **Developer Centre > Your Games > Game Services > Application Configuration**.
-- The API domain name is viewable at **Application Configuration > Domain Configuration > API**. Refer to the documentation for [domain name](/sdk/domain/guide/) for more information.
-
-
-
-Here
-`userId` serves as a unique identifier for the client connecting to the server.
-Note that this `userId` has the following restrictions:
-
-- Only letters, numbers, and underscores are allowed
-- Cannot exceed 32 characters in length
-- Globally unique within an application
-
-`gameVersion` indicates the version number of the client, which can be used to route to different game servers if multiple versions of the game are allowed to co-exist.
-
-## Connection
-
-### Establishing a connection
-
-Connect the current player to the Multiplayer service with the following code:
-
-```javascript
-client
- .connect()
- .then(() => {
- // Connection successful
- })
- .catch((error) => {
- // connection failure
- console.error(error.code, error.detail);
- });
-```
-
-## Lobby
-
-We recommend that you **don't add players to the lobby**, because in the lobby, the server will constantly send out the latest full list of rooms, and players will choose one of the rooms to play by themselves, which is not only unfriendly to the player experience, but also brings a lot of bandwidth pressure.
-We recommend you to **use [Room Matching](#room-matching) like most of the mobile games nowadays to quickly match players and let them start the game**.
-
-If you have a special game scenario where you need to get the room list, you can call the following method:
-
-```javascript
-client
- .joinLobby()
- .then(() => {
- // Join Lobby Successfully
- })
- .catch((error) => {
- // Failed to join the lobby
- console.error(error.code, error.detail);
- });
-```
-
-When a player joins the lobby, the server sends the list of rooms in the current lobby to the client, and the developer can view the list of rooms or join a room to join the game as needed.
-
-```javascript
-client.on(Event.LOBBY_ROOM_LIST_UPDATED, () => {
- const roomList = client.lobbyRoomList;
- // TODO can do the logic for displaying the list of rooms.
-});
-```
-
-For more on `LobbyRoom`, see [API documentation](https://leancloud.github.io/Play-SDK-JS/doc/LobbyRoom.html).
-
-### Related events
-
-| Event | Parameter | Description |
-| ----------------------- | ---- | ---------------- |
-| LOBBY_ROOM_LIST_UPDATED | None | Lobby Room List Update |
-
-## Room Matching
-
-A room is a unit that generates "combat interactions" between players. For example, the card table of Landlord, the copy of MMO, the battle of WeChat game, etc., all belong to the category of room in a broad sense.
-The combat interactions between players are all done in the room. Therefore, how players enter a room is the key to room matching. Below we will analyse the commonly used "Room Matching" function from the aspects of "Create Room" and "Join Room".
-
-### Creating a Room
-
-We can create a room simply like this. The player who creates the room is the room owner ([MasterClient](#masterclient)), and when the owner creates a room successfully, he/she also joins the room.
-
-```javascript
-client
- .createRoom()
- .then(() => {
- // Successful room creation also means that you have successfully joined the room.
- })
- .catch((error) => {
- // Failed to create room
- console.error(error.code, error.detail);
- });
-```
-
-We can also create a room that sets the relevant information.
-
-```javascript
-// Room customisation properties
-const props = {
- title: "room title",
- level: 2,
-};
-const options = {
- // The room is not visible
- visible: false,
- // Retention time after room emptying, in seconds
- emptyRoomTtl: 300,
- // Maximum number of players allowed
- maxPlayerCount: 2,
- // The amount of time, in seconds, that the player's data is retained after the player goes offline.
- playerTtl: 300,
- customRoomProperties: props,
- // Custom attribute key for room matching, i.e. room matching condition is level = 2
- customRoomPropertyKeysForLobby: ["level"],
- // Setting permissions on the MasterClient
- flag:
- CreateRoomFlag.MasterSetMaster | CreateRoomFlag.MasterUpdateRoomProperties,
-};
-const expectedUserIds = ["world"];
-client
- .createRoom({
- roomName,
- roomOptions: options,
- expectedUserIds: expectedUserIds,
- })
- .then(() => {
- // Successful room creation also means that you have successfully joined the room.
- })
- .catch((error) => {
- console.error(error.code, error.detail);
- });
-```
-
-The parameters `roomName`, `roomOptions` and `expectedUserIds` are optional.
-
-#### roomName
-
-The room name must be unique; if it is not set, the server generates a unique room id and returns it.
-
-#### roomOptions
-
-Specified parameters when creating a room, including:
-
-- `Opened`: whether the room is open or not. If set to false, no other players are allowed to join.
-- `Visible`: whether the room is visible. If set to false, it will not appear in the lobby's room list, but other players can join the room by specifying the room name.
-- `EmptyRoomTtl`: how long (in seconds) the room will be kept when there are no players in the room. Default is 0, i.e. the room data will be destroyed immediately when there are no players in the room. Maximum value is 300, i.e. 5 minutes.
-- `PlayerTtl`: the time (in seconds) to keep the player's data in the room when the player drops out. Default is 0, i.e. player data is destroyed immediately after the player drops out. Maximum value is 300, i.e. 5 minutes.
-- `MaxPlayerCount`: the maximum number of players allowed in the room.
-- `CustomRoomProperties`: custom properties for the room.
-- `CustomRoomPropertyKeysForLobby`: an array of keys in `CustomRoomProperties`. The properties contained in `CustomRoomPropertyKeysForLobby` will appear in the lobby's room Properties (`client.LobbyRoomList`), and the full set of properties can be viewed in `room.CustomProperties` after joining the room. These properties will be used when matching rooms.
-- `Flag`: flag bit for room creation. See [MasterClient drop without transfer](#masterclient-drop-transfer), [Specify other member as MasterClient](#specify-other-members-as-masterclient), and [Allow only MasterClient to modify room properties](#allow-only-masterclient-to-modify-room-properties) below for more details.
-
-#### expectedUserIds
-
-An array of specified player IDs. This parameter is mainly used to "reserve space" for certain players who can join the room.
-
-Note: These "specific players" will not actually join the room. There will only be space in the room reserved for those "specific players" to join. If you want to invite players to join a room, you need to send the room name to your friends through other channels, such as IM, WeChat, etc., and then your friends will join the room through the `JoinRoom(roomName)` interface.
-
-For more information about `CreateRoom`, please refer to [API Documentation](https://leancloud.github.io/Play-SDK-JS/doc/Client.html#createRoom).
-
-### Joining a room
-
-When a room is created, other players can participate in the game by "joining the room".
-
-#### Join the designated room
-
-You can join a room by specifying the room name.
-
-```javascript
-// Players are joining the 'LiLeiRoom' room.
-client
- .joinRoom("LiLeiRoom")
- .then(() => {
- // Join the room successfully
- })
- .catch((error) => {
- // Failed to join room
- console.error(error.code, error.detail);
- });
-```
-
-When joining a room, you can also take up space for other players. If the remaining empty space in the room is less than the number of occupied spaces, you will fail to join the room.
-
-```javascript
-const expectedUserIds = ["LiLei", "Jim"];
-// Players are joining the 'game' room and taking up space for the hello and world players.
-client
- .joinRoom("game", {
- expectedUserIds,
- })
- .then(() => {
- // Join the room successfully
- })
- .catch((error) => {
- // Failed to join room
- console.error(error.code, error.detail);
- });
-```
-
-For more on `joinRoom`, see [API documentation](https://leancloud.github.io/Play-SDK-JS/doc/Client.html#joinRoom).
-
-#### Randomly joining rooms
-
-Sometimes, instead of joining a specific room, we can randomly join "rooms that meet certain conditions" (or even no conditions), such as quick start, quick match, etc. In this case, we can randomly join rooms by calling the `joinRandomRoom` method.
-
-```javascript
-client
- .joinRandomRoom()
- .then(() => {
- // Join the room successfully
- })
- .catch((error) => {
- // Failed to join room
- console.error(error.code, error.detail);
- });
-```
-
-It is also possible to set "conditions" for random joining, such as randomly joining a room with level = 2.
-
-```javascript
-// Set the match attribute
-const matchProps = {
- level: 2,
-};
-client
- .joinRandomRoom({
- matchProperties: matchProps,
- })
- .then(() => {
- // Join the room successfully
- })
- .catch((error) => {
- // Failed to join room
- console.error(error.code, error.detail);
- });
-```
-
-### Join or create a specific room
-
-Many games don't have scenarios that force a designated room owner. As long as a number of players can play in the same room, it is fine for anyone to be the owner. We can use `joinOrCreateRoom()` to achieve this. This method will join the current player directly to the room if there is a relevant room, or create a new room if there isn't such a room.
-
-```javascript
-// For example, if 4 players join a room with the name "room1" at the same time, if it doesn't exist, then create and join the room with the name "room1".
-client
- .joinOrCreateRoom("room1")
- .then(() => {
- // Join or create a room successfully
- })
- .catch((error) => {
- // Failed to join a room and did not successfully create a room
- console.error(error.code, error.detail);
- });
-```
-
-For more on `joinOrCreateRoom`, see the [API documentation](https://leancloud.github.io/Play-SDK-JS/doc/Client.html#joinOrCreateRoom).
-
-### New player join event
-
-For players who are already in the room, when a new player joins the room, the server will send the `PLAYER_ROOM_JOINED` event to notify the client, and the client can use the properties of the new player to do some display logic.
-
-```javascript
-// Register new players to join the event
-client.on(Event.PLAYER_ROOM_JOINED, ({ newPlayer }) => {
- // TODO New Player Joining Logic
-});
-```
-
-You can get all the players in the room by `client.room.playerList`.
-
-### Determining local players
-
-Once you have all the players in the room, you can determine if a `Player` is the current local player.
-
-```javascript
-const players = client.room.playerList;
-const player = players[0];
-const isLocal = player.isLocal;
-```
-
-### Leaving a room
-
-The following interface can be called when the player wants to proactively leave the room.
-
-```javascript
-client
- .leaveRoom()
- .then(() => {
- // Leaving the room successfully allows you to execute logic such as jumping scenes
- })
- .catch((error) => {
- console.error(error.code, error.detail);
- });
-```
-
-Other players in the room will receive the `PLAYER_ROOM_LEFT` event.
-
-```javascript
-// Registering a player leaving the room event
-client.on(Event.PLAYER_ROOM_LEFT, ({ leftPlayer }) => {
- // TODO Can perform the destruction of the player's departure
-});
-```
-
-### Related events
-
-| Event | Parameter | Description |
-| ------------------ | -------------- | -------------- |
-| PLAYER_ROOM_JOINED | { newPlayer } | A new player joined the room |
-| PLAYER_ROOM_LEFT | { leftPlayer } | A player left the room |
-
-## MasterClient
-
-The room creator will become the MasterClient by default in the Multiplayer service. They can close the room, set the room to be invisible, kick people, and so on. Their device is also responsible for "logical operations". For example, it can be responsible for the following scenarios in the game:
-
-- Dealing cards for users in card games;
-- Controlling the timing and logic of killing monsters;
-- Judging the winner at the end of the game;
-- ...and so on.
-
-### MasterClient located on the player's client
-
-You can write the MasterClient logic in the client, and the player terminal ofthe room creator will take care of the game operations. In this case, Multiplayer provide the following convenient features for MasterClient:
-
-#### MasterClient Drop Transfer
-
-When a MasterClient is dropped, the Multiplayer service assigns a new player client as the MasterClient. Even if the original MasterClient comes back online, it does not become the new MasterClient. When the MasterClient is changed, the SDK dispatches the `MASTER_SWITCHED` event to notify the client when the MasterClient changes.
-
-```javascript
-// Registering for host switching events
-client.on(Event.MASTER_SWITCHED, ({ newMaster }) => {
- // TODO can do host switching display
-
- // You can determine whether to perform logical processing based on determining whether the current client is a Master.
- if (client.player.isMaster) {
- }
-});
-```
-
-#### Related Events
-
-| Event | Parameter | Description |
-| --------------- | ------------- | ----------- |
-| MASTER_SWITCHED | { newMaster } | Master changed |
-
-#### Specify other members as MasterClient
-
-During the game, if the MasterClient does not want to take the computing function anymore, it can call the following method to actively transfer its role to other player clients:
-
-```javascript
-// Assign MasterClient by Player Id
-client
- .setMaster(otherActorId)
- .then(() => {
- // The value here is false
- console.log(client.player.isMaster);
- })
- .catch(console.error);
-```
-
-After transferring the MasterClient identity, all players in the room receive the `MASTER_SWITCHED` event.
-
-### MasterClient on the server
-
-In order to ensure game security and prevent users from cheating, we recommend that you host your MasterClient in the Client Engine provided by TDS and configure the following permissions:
-
-#### MasterClient will not be transferred when it drops
-
-After placing the MasterClient on the server, the MasterClient role should not be transferred even if it unexpectedly drops out of the game, in which case you need to specify the `FixedMaster` flag when creating a room:
-
-```js
-client.createRoom({
- roomOptions: {
- flag: CreateRoomFlag.FixedMaster,
- },
-});
-```
-
-### MasterClient operation
-
-#### Set whether a room is open or not
-
-MasterClient can set whether a room is open or not, so that other players are not allowed to join when the room is closed.
-
-```javascript
-// Setting the room to close
-client
- .setRoomOpen(false)
- .then(() => {
- console.log(client.room.open);
- })
- .catch((error) => {
- console.error(error.code, error.detail);
- });
-```
-
-#### Setting whether a room is visible or not
-
-MasterClient can set whether a room is visible or not. When the room is not visible, this room will not appear in the player's lobby room list, but **other players can join by specifying roomName**.
-
-```javascript
-// Setting the room invisible
-client
- .setRoomVisible(false)
- .then(() => {
- console.log(client.room.visible);
- })
- .catch((error) => {
- console.error(error.code, error.detail);
- });
-```
-
-#### Kicking people
-
-MasterClient can kick other players in a room out of the room:
-
-```javascript
-// You can pass in an Object object, which only supports the code and msg keys.
-var info = { code: 1, msg: "You've been kicked out of your room." };
-client.kickPlayer(otherPlayer.actorId, info).then(() => {
- console.log("Successfully kicked out of the room");
-});
-```
-
-After being kicked out of a room, the kicked player receives the `ROOM_KICKED` event.
-
-```javascript
-client.on(Event.ROOM_KICKED, ({ code, msg }) => {
- // code and msg are what the MasterClient passes when it kicks someone.
-});
-```
-
-At the same time, players other than the MasterClient who still have a room will receive the `PLAYER_ROOM_LEFT` event:
-
-```javascript
-client.on(Event.PLAYER_ROOM_LEFT, ({ leftPlayer }) => {});
-```
-
-## Custom Attributes and Synchronisation
-
-In order to meet the different game requirements of developers, the Online Battle SDK allows developers to set "custom properties". The custom property interface parameters are defined as JavaScript `Object` types, and the supported data types include:
-
-- Boolean
-- Number
-- String
-- Object
-- Array
-
-The main functions of custom property synchronization include:
-
-- Keeping the data consistent among multiple clients.
-- Custom attributes are managed by the server. When a player enters a room, all custom attributes will be available.
-
-Custom attributes are divided into "Room Custom Attributes" and "Player Custom Attributes".
-
-### Room Custom Attributes
-
-You can set a `Object` type custom attribute for a room, such as the number of rounds in a battle, all the pieces in a room, and so on.
-
-```javascript
-// Set the custom properties you want to modify
-const props = {
- gold: 1000,
-};
-// Set the gold property to 1000
-client.room
- .setCustomProperties(props)
- .then(() => {
- var newProperties = client.room.customProperties;
- })
- .catch(console.error);
-```
-
-Note: This interface does not directly set the `Memory values of custom properties in the client`, but rather sends a message to modify the custom properties, with the server making the final determination of whether to do so or not.
-
-When a room property is changed, the SDK will dispatch the `ROOM_CUSTOM_PROPERTIES_CHANGED` event to notify all player clients (including itself).
-
-```javascript
-// Registering room property change events
-client.on(Event.ROOM_CUSTOM_PROPERTIES_CHANGED, ({ changedProps }) => {
- // The full properties of the room can be obtained from this method
- const properties = client.room.customProperties;
- const gold = properties.gold;
- // TODO can do the interface display of attribute changes.
-});
-```
-
-Note: The `changedProps` parameter only indicates incrementally changed parameters, not `all properties`. To get all properties, please get them via `client.room.customProperties`.
-
-#### Allow only MasterClient to modify room properties
-
-By default, all clients in a room can modify the room's custom properties. If you want to allow only the MasterClient to do so, you can specify the `MasterUpdateRoomProperties` flag when creating the room:
-
-```js
-client
- .createRoom({
- roomOptions: {
- flag: CreateRoomFlag.MasterUpdateRoomProperties,
- },
- })
- .then(() => {
- // Room Creation Successful
- })
- .catch(console.error);
-```
-
-### Player custom attributes
-
-Player custom attributes are essentially the same as [room custom attributes](#room-custom-attributes).
-
-```javascript
-// Poker Objects
-const poker = {
- // design and colour
- flower: 1,
- // numerical value
- num: 13,
-};
-const props = {
- nickname: "Li Lei",
- gold: 1000,
- poker: poker,
-};
-// Request to set player attributes
-client.player
- .setCustomProperties(props)
- .then(() => {
- // Setting properties succeeded
- })
- .catch(console.error);
-```
-
-Any player in the room (including yourself) who modifies their custom attributes triggers the Player Custom Attribute Change event:
-
-```javascript
-// Registering for player-defined attribute change events
-client.on(Event.PLAYER_CUSTOM_PROPERTIES_CHANGED, ({ player }) => {
- // Get all custom attributes of the player
- const props = player.customProperties;
- const { title, gold } = props;
- // TODO You can do an interface display of property changes
-});
-```
-
-### CAS
-
-CAS stands for Compare And Swap, which means check and update, and is used to avoid some concurrency problems.
-
-When `SetCustomProperties` is called, the server receives all the values submitted by the client, which is not enough for some scenarios.
-
-For example, a property holds the holder of an item in this room, i.e. the key of the property is the item and the value is the holder.
-Any client can set this property at any time, and if multiple clients call it at the same time, **the server will take the last call received as the final value**, which is usually illogical. Usually the item should belong to the first person who got it.
-
-`SetCustomProperties` has an optional parameter `expectedValues` that can be used as a judgement condition. The server will only update properties that currently match `expectedValues`. Updates with expired `expectedValues` will be ignored.
-
-Suppose there are 10 players in a room, but there is only 1 Tulong Knife, and the Tulong Knife can only be won by the first person who "grabs it".
-
-We can set the `tulong` value in `expectedValues`:
-
-```javascript
-const props = {
- tulong: X, // X denotes the current client, respectively
-};
-const expectedValues = {
- tulong: null, // Current owner of the Dragon Slayer
-};
-client.room
- .setCustomProperties(props, { expectedValues })
- .then(() => {})
- .catch(console.error);
-```
-
-This way, after the first player gets the Tulong Knife, `tulong` corresponds to the value of first player, and subsequent requests with (`const expectedValues = { tulong: null }`) will fail.
-
-### Related events
-
-| Events | Parameters | Description |
-| -------------------------------- | ------------------------ | ------------------ |
-| ROOM_CUSTOM_PROPERTIES_CHANGED | { changedProps } | Room custom property is changed |
-| PLAYER_CUSTOM_PROPERTIES_CHANGED | { player, changedProps } | Player custom property is changed |
-
-## Custom events
-
-In [Custom Properties](#custom-attributes-and-synchronisation), we introduced how to customize the game's data structures and data types based on the game's requirements.
-However, data by itself is not enough. To allow different parties to communicate, custom events are also needed.
-
-### Sending custom events
-
-We can send various events through custom events, such as game start, card capture, release X skill, game end, etc.
-
-```javascript
-const options = {
- // Set the receiving group of the event to MasterClient
- receiverGroup: ReceiverGroup.MasterClient,
- // You can also specify the recipient actorId
- // options.targetActorIds: [1],
-};
-// Setting Skill Id
-const eventData = {
- skillId: 123,
-};
-
-// Set Event Id
-const SKILL_EVENT_ID = 1;
-// Send custom events
-client
- .sendEvent(SKILL_EVENT_ID, eventData, options)
- .then(() => {})
- .catch(console.error);
-```
-
-The `options` are the event sending parameters, including the receiving group and the receiver ID array.
-
-- ReceiverGroup is an enumeration of the targets of the received event, including Others (everyone in the room except yourself), All (everyone in the room), and MasterClient (the host).
-- The Receiver ID array is the enumerated value of the target of the received event, i.e., the player's `actorId` array. The `actorId` can be obtained via `player.actorId`.
-
-Note: If both receiver group and receiver ID array are set, receiver ID array will override receiver group.
-
-### Receiving custom events
-
-When the event is sent successfully, the custom event `CUSTOM_EVENT` in the event receiver will be triggered, and different events can be handled according to `eventId` (event ID).
-
-```javascript
-// Registering custom events
-client.on(Event.CUSTOM_EVENT, ({ eventId, eventData }) => {
- if (eventId === SKILL_EVENT_ID) {
- // Deconstruct event data if it is a skill event
- const { skillId, targetId } = eventData;
- // TODO Handling the performance of released skills
- }
-});
-```
-
-### `event` Parameters
-
-| parameter | type | description |
-| --------- | ------ | ------------------------------- |
-| eventId | Number | Event Id for the event |
-| eventData | Object | Event parameter |
-| senderId | Number | Event sender ID (player's actorId)|
-
-## Disconnect
-
-In case of unstable network, you may be disconnected passively. When you are disconnected passively, the SDK will send `DISCONNECTED` event to the client, where the developer can alert the player on the UI:
-
-```javascript
-// Registering for disconnection events
-client.on(Event.DISCONNECTED, () => {
- // TODO Option to detect the network and reconnect if required
-});
-```
-
-## Disconnect and reconnect
-
-### Keep disconnected users in the room.
-
-Players may get disconnected when there's unstable network or when they put the game in the background. When the player drops out, the `PLAYER_ACTIVITY_CHANGED` event will be triggered for other online players:
-
-```javascript
-// Registered Player Dropping/Uploading Events
-client.on(Event.PLAYER_ACTIVITY_CHANGED, ({ player }) => {
- // Get the status of whether a user is "active" or not
- cc.log(player.isActive());
- // TODO can do display and logic processing according to the player's online status.
-});
-```
-
-If a player doesn't come back to the game for a long time, the server will remove the player from the room and destroy the player's data, and the other players in the room will receive the `PLAYER_ROOM_LEFT` event.
-When creating a room, we can set the timeout via `PlayerTtl`, so that when a player drops out of the room, the server will keep the data of the dropped player in the room during the `PlayerTtl` time and wait for the player to come back online.
-
-```javascript
-const options = {
- // Set playerTtl to 300 seconds
- playerTtl: 300,
-};
-client
- .createRoom({
- roomOptions: options,
- })
- .then(() => {})
- .catch(console.error);
-```
-
-When a dropped player returns to the room within the `PlayerTtl` time, the other player's `PLAYER_ACTIVITY_CHANGED` event will be triggered again, and the developer can determine the player's current state in this event.
-
-### Reconnect
-
-Users can reconnect to the server after being disconnected through the following interface.
-
-```javascript
-client
- .reconnect()
- .then(() => {
- // Reconnect successfully, you can choose whether to return to the room or not at this time
- })
- .catch(console.error);
-```
-
-Note: This interface will only reconnect you to the server, not return you directly to the room if you were previously playing in the room.**Recommended** [Reconnect and get room](#rejoin-a-room), then the player can choose whether or not to return to the room.You can also just [Reconnect and return to room](#reconnect-and-return-to-room).
-
-### Returning to a room
-
-Once a player is connected to the server, they can "rejoin" a room via the `rejoin` interface. If this interface is called within `playerTtl` time, the player will be able to return to the room successfully, if the `playerTtl` time is exceeded, then rejoining the room will fail.
-
-```javascript
-client
- .reconnect()
- .then(() => {
- // The reconnection was successful. We're back in the same room as before.
- return client.rejoinRoom(roomName); // The roomName of the room needs to be cached in the client itself.
- })
- .then(() => {
- // If you return to the room successfully, the other players in the room will receive the `PLAYER_ACTIVITY_CHANGED` event.
- })
- .catch(console.error);
-```
-
-### Reconnect and return to room
-
-This interface is the equivalent of `reconnect()` and `rejoinRoom()` combined. This interface allows you to directly reconnect and go back to the previous room.
-
-```javascript
-client
- .reconnectAndRejoin()
- .then(() => {
- // Return to room success, update data and interface
- })
- .catch(console.error);
-```
-
-## Close
-
-Developers can also proactively close the SDK via the following interface, after which the Client needs to be re-instantiated if it needs to be used again.
-
-```javascript
-client.close().then(() => {
- // Disconnect successful.
-});
-```
-
-## Error handling
-
-Specific exception messages can be caught with Promise catch when we initiate a request. For example, when creating a room:
-
-```javascript
-client
- .createRoom()
- .then(() => {
- // Room Creation Successful
- })
- .catch((error) => {
- console.error(error.code, error.detail);
- });
-```
-
-## Critical error event
-
-If a major error is currently occurring, this event will be triggered. It won't be triggered easily, and if it is triggered, please contact technical support to deal with it.
-
-```javascript
-client.on(Event.Error, ({ code, detail }) => {
- // Contact Technical Support
-});
-```
-
-## Serialisation
-
-JavaScript is a weakly typed language, so data can be synchronised as long as it is of type `Object/Array`.
-
-### Custom types
-
-Suppose we have a Hero type with id, name, hp defined as follows:
-
-```javascript
-class Hero {
- constructor(id, name, hp) {
- this._id = id;
- this._name = name;
- this._hp = hp;
- }
-}
-```
-
-To synchronise data of type Hero, the following two steps are required:
-
-#### Implement serialisation / deserialisation methods
-
-The implementation of serialisation methods is up to the developer, you can use protobuf, thrift, etc. It is sufficient if the serialisation and deserialisation interfaces supported by `Play` are met. As long as the serialisation and deserialisation interfaces supported by `Play` are satisfied.
-
-The following is an example of how to serialise an `Object` with the serialisation methods provided by `Play`:
-
-```javascript
-const {
- serializeObject,
- deserializeObject,
-} = Play;
-
-// serialisation
-static serialize(hero) {
- // You can filter the fields to be serialised
- const obj = {
- id: hero._id,
- name: hero._name,
- hp: hero._hp,
- };
- return serializeObject(obj);
-}
-
-// deserialisation
-static deserialize(bytes) {
- const obj = deserializeObject(bytes);
- const { id, name, hp } = obj;
- const hero = new Hero(id, name, hp);
- return hero;
-}
-```
-
-#### Registering Custom Types
-
-When implementing a serialisation method, remember to register the custom type before using it.
-
-```javascript
-const { registerType } = Play;
-
-registerType(Hero, typeCode, Hero.serialize, Hero.deserialize);
-```
-
-where `typeCode` is a numeric code representing the custom type, which is used to determine the custom type during deserialisation.
-
-## API Documentation
-
-For more interfaces and details, please refer to [API Interfaces](https://leancloud.github.io/Play-SDK-JS/doc/).
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/start/cs.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/start/cs.mdx
deleted file mode 100644
index 5460b9e6b..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/start/cs.mdx
+++ /dev/null
@@ -1,201 +0,0 @@
----
-title: Getting Started with Multiplayer - C#
-sidebar_label: C#
-sidebar_position: 2
----
-
-import { Conditional } from "/src/docComponents/conditional";
-import sdkVersions from "/src/docComponents/sdkVersions";
-import CodeBlock from "@theme/CodeBlock";
-
-Welcome to Multiplayer. This tutorial will explain the core usage of the SDK by simulating a scenario that compares players' scores.
-
-## Installation
-
-The Play client SDK is open source. The source code can be found at [csharp-sdk](https://github.com/leancloud/csharp-sdk).
-
-You can also download [Release version](https://github.com/leancloud/csharp-sdk/releases).
-
-### Unity Projects
-
-The SDK can be imported via Unity Package Manager or manually:
-
-* **UPM**: Please add dependencies in Packages/manifest.json of your project:
-
-
- {`"dependencies": {
- "com.leancloud.play": "https://github.com/leancloud/csharp-sdk-upm.git#play-${sdkVersions.leancloud.csharp}",
- }`}
-
-
-* **Direct Import**: Unzip the downloaded LeanCloud-SDK-Play-Unity.zip and drag and drop the `Plugins` directory into the Unity project. If there is already a `Plugins` directory in the project, merge the SDK into the `Plugins` directory in the project.
-
-### Turn on debug logging
-
-For debugging purposes, you can register callbacks for logging. In Unity, you can refer to the following settings:
-
-```cs
-// Setting the SDK Log Delegation
-LeanCloud.Common.Logger.LogDelegate = (level, log) =>
-{
- if (level == LogLevel.Debug) {
- Debug.LogFormat("[DEBUG] {0}", log);
- } else if (level == LogLevel.Warn) {
- Debug.LogWarningFormat("[WARN] {0}", log);
- } else if (level == LogLevel.Error) {
- Debug.LogErrorFormat("[ERROR] {0}", log);
- }
-};
-```
-
-## Initialisation
-
-Import the required namespaces
-
-```cs
-using LeanCloud.Play;
-```
-
-
-
-```cs
-var client = new Client("your-app-id", "your-app-key", "tarara", playServer: "https://xxx.example.com");
-// Please replace xxx.example.com with the domain name of the custom API your app is bound to.
-```
-
-
-
-
-
-```cs
-var client = new Client(
- "your-client-id", // Client ID of the game
- "your-client-token", // Client Token for the game
- "tarara", // Set the user id
- playServer: "https://your_server_url" // The game's API domain name
-);
-```
-
-- The `Client ID` and `Client Token` of the game can be viewed at **Developer Centre > Your Games > Game Services > Application Configuration**.
-- The API domain name is viewable at **Application Configuration > Domain Configuration > API**. Refer to the documentation for [domain name](/sdk/domain/guide/) for more information.
-
-
-
-## Connecting to a Multiplayer server
-
-```cs
-try {
- await client.Connect();
-} catch (PlayException e) {
- // connection failure
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-## Creating or Joining a Room
-
-By default the Play SDK does not require a "lobby" to create/join a room.
-
-```cs
-try {
- await client.JoinOrCreateRoom(roomName);
-} catch (PlayException e) {
- // Failed to create or join a room
- Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
-}
-```
-
-`JoinOrCreateRoom` ensures that two client players can enter the same room by having the same roomName. Please refer to the [Developer Guide](/sdk/multiplayer/guide/cs/#joinorcreateroom) for more information on the usage of `JoinOrCreateRoom`.
-
-## Synchronising Player Properties via CustomPlayerProperties
-
-When a new player joins the room, the Master assigns a score to each player, which is synchronised to the players via "Player Custom Properties".
-(No more complex algorithms are done here, just 10 points are assigned to the Master and 5 points to the other players).
-
-```cs
-// Register the event for new players joining the room
-client.OnPlayerRoomJoined += (newPlayer) => {
- Debug.LogFormat("new player: {0}", newPlayer.UserId);
- if (client.Player.IsMaster) {
- // Get a list of players in the room
- var playerList = client.Room.PlayerList;
- for (int i = 0; i < playerList.Count; i++) {
- var player = playerList[i];
- var props = new PlayObject();
- // Set 10 points for the master and 5 points for other players
- if (player.IsMaster) {
- props.Add("point", 10);
- } else {
- props.Add("point", 5);
- }
- player.SetCustomProperties(props);
- }
- var data = new PlayObject {
- { "winnerId", client.Room.Master.ActorId }
- };
- var opts = new SendEventOptions {
- ReceiverGroup = ReceiverGroup.All
- };
- client.SendEvent(GAME_OVER_EVENT, data, opts);
- }
-};
-```
-
-Display the score after the player gets their score.
-
-```cs
-// Register for "Player Attribute Change" event
-client.OnPlayerCustomPropertiesChanged += (player, changedProps) => {
- // Determine if the player is themselves and do the UI display
- if (player.IsLocal) {
- // Get the player's score
- long point = player.CustomProperties.GetInt("point");
- Debug.LogFormat("{0} : {1}", player.UserId, point);
- scoreText.text = string.Format("Score: {0}", point);
- }
-};
-```
-
-## Communication via Custom Events
-
-When the score is distributed, the ID of the winner (Master) is sent as an argument to all players via a custom event.
-
-```cs
-if (client.Player.IsMaster) {
- // ...
- var data = new PlayObject {
- { "winnerId", client.Room.Master.ActorId }
- };
- var opts = new SendEventOptions {
- ReceiverGroup = ReceiverGroup.All
- };
- client.SendEvent(GAME_OVER_EVENT, data, opts);
-}
-```
-
-Make different UI displays depending on whether you judge the winner to be yourself or not.
-
-```cs
-// Registering custom events
-client.OnCustomEvent += (eventId, eventData, senderId) => {
- if (eventId == GAME_OVER_EVENT) {
- // Get the winner's Id
- int winnerId = eventData.GetInt("winnerId");
- // If the winner is yourself, display the victory UI; otherwise display the defeat UI
- if (client.Player.ActorId == winnerId) {
- Debug.Log("win");
- resultText.text = "Win";
- } else {
- Debug.Log("lose");
- resultText.text = "Lose";
- }
- client.Close();
- }
-};
-```
-
-## Demo
-
-We have completed this Demo through Unity for you to run for reference.
-
-[QuickStart Project](https://github.com/leancloud/Play-CSharp-Quick-Start).
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/start/js.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/start/js.mdx
deleted file mode 100644
index 7dbe43410..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/multiplayer/start/js.mdx
+++ /dev/null
@@ -1,332 +0,0 @@
----
-title: Getting Started with Multiplayer - JavaScript
-sidebar_label: JavaScript
-sidebar_position: 1
----
-
-import { Conditional } from "/src/docComponents/conditional";
-
-Welcome to Multiplayer. This tutorial will explain the core usage of the SDK by simulating a scenario that compares players' scores.
-
-## Installation
-
-The Multiplayer Client SDK is open source. You can download [Release version](https://github.com/leancloud/Play-SDK-JS/releases) directly. For source code, please visit [Play-SDK-JS](https://github.com/leancloud/Play-SDK-JS).
-
-## Supported Development Platforms
-
-WeChat Developer Tools: WeChat Mini Programs / WeChat Mini Games
-
-CocosCreator: Mac, Web, WeChat Mini Games, Facebook Instant Games, iOS, Android.
-
-LayaAir: WeChat Small Game
-
-Egret:Web
-
-### Cocos Creator
-
-[Download play.js](https://github.com/leancloud/Play-SDK-JS/releases) and drag and drop it into your Cocos Creator project, select "Plugin" mode to import. See [Cocos Creator plugin script](https://docs.cocos.com/creator/manual/zh/scripting/external-scripts.html).
-
-Select the play.js file you just imported in Cocos Creator, and check all the following options in the Property Inspector:
-
-- Import as plugin
-- Allow web platform loading
-- Allow editor loading
-- Allow Native platform loading
-
-As shown in the figure:
-
-![image](/img/multiplayer/cocos-creator-multiplayer-install.png)
-
-### LayaAir
-
-[Download play-laya.js](https://github.com/leancloud/Play-SDK-JS/releases) to the bin/libs directory of your Laya project.
-
-In bin/index.html, introduce the SDK file you just downloaded before the item "UI file generated by IDE":
-
-```diff
-
-
-
-
-+
-
-
-
-```
-
-### Egret
-
-[Download play-egret.zip](https://github.com/leancloud/Play-SDK-JS/releases) and extract it to the libs directory of your Egret project.
-
-Add the SDK configuration to the egretProperties.json file in the Egret project:
-
-```diff
-{
- "engineVersion": "5.2.13",
- "compilerVersion": "5.2.13",
- "template": {},
- "target": {
- "current": "web"
- },
- "modules": [
- {
- "name": "egret"
- },
- ...
-+ {
-+ "name": "Play",
-+ "path": "./libs/play"
-+ }
- ]
-}
-```
-
-Under the Egret project, execute the `Egret build -e` command, and if a reference to the SDK is generated in manifest.json, the SDK has been installed successfully.
-
-```diff
-{
- "initial": [
- "libs/modules/egret/egret.js",
- ...
-+ "libs/play/Play.js"
- ],
- "game": [
- ...
- ]
-}
-```
-
-You can refer to [Egret third-party library usage](https://docs.egret.com/engine/docs/extension/threes/instructions).
-
-### WeChat Mini Program
-
-[Download play-weapp.js](https://github.com/leancloud/Play-SDK-JS/releases) and drag and drop it to the project directory of WeChat Mini Program.
-
-### Node.js installation
-
-Install and reference the SDK:
-
-```sh
-npm install @leancloud/play --save
-```
-
-## Logging
-
-Logging can be used to track down problems. The SDK supports debugging with logging turned on in both browser and Node.js environments. When debugging logging is enabled, the SDK will output network requests, error messages, and other information to the console.
-
-### Browser
-
-In browser environment, please open the console of your browser and run the following command:
-
-```shell
-localStorage.debug = 'Play'
-```
-
-### Node.js
-
-In a Node.js environment, you need to set the environment variable DEBUG to Play, which you can set before starting a command.
-Here's an example of a command to start Cloud Engine debugging locally, `lean up`:
-
-```sh
-# Unix
-DEBUG='Play' lean up
-# Windows cmd
-set DEBUG=Play lean up
-```
-
-## Initialisation
-
-Importing the SDK
-
-```javascript
-const {
- Client,
- Region,
- Event,
- ReceiverGroup,
- setAdapters,
- LogLevel,
- setLogger,
-} = Play;
-```
-
-
-
-```javascript
-const client = new Client({
- // Setting the APP ID
- appId: "your-app-id",
- // Setting the APP Key
- appKey: "your-app-key",
- // Setting up the Server (please replace xxx.example.com with the custom API domain name your app is bound to)
- playServer: 'https://xxx.example.com',
- // Setting the user id
- userId: 'tarara',
- // Set game version number (optional, default 0.0.1)
- gameVersion: '0.0.1'
-});
-```
-
-
-
-
-
-```js
-const client = new Client({
- appId: 'your-client-id', // Client ID of the game
- appKey: 'your-client-token', // Client Token for the game
- playServer: 'https://your_server_url', // The game's API domain name
- userId: 'tarara', // Set the user id
- gameVersion: '0.0.1' // Set the game version number, optional, default 0.0.1, players of different versions will not be matched in the same room.
-});
-```
-
-- The `Client ID` and `Client Token` of the game can be viewed at **Developer Centre > Your Games > Game Services > Application Configuration**.
-- The API domain name is viewable at **Application Configuration > Domain Configuration > API**. Refer to the documentation for [domain name](/sdk/domain/guide/) for more information.
-
-
-
-## Connecting to a Multiplayer server
-
-```javascript
-client
- .connect()
- .then(() => {
- // Connection successful
- })
- .catch((error) => {
- // connection failure
- console.error(error.code, error.detail);
- });
-```
-
-## Creating or Joining a Room
-
-By default the Play SDK does not require a "lobby" to create/join a room.
-
-```javascript
-// For example, if 4 players join a room with the name "room1" at the same time, and if it doesn't exist, then create and join the
-client
- .joinOrCreateRoom("room1")
- .then(() => {
- // Join or create a room successfully
- })
- .catch((error) => {
- // Failed to join a room and did not successfully create a room
- console.error(error.code, error.detail);
- });
-```
-
-`joinOrCreateRoom` ensures that two client players can enter the same room by having the same roomName. Please refer to the [Developer Guide](/sdk/multiplayer/guide/js/#join-or-create-a-specific-room) for more information on the usage of `joinOrCreateRoom`.
-
-## Synchronising Player Properties via CustomPlayerProperties
-
-When a new player joins the room, the Master assigns a score to each player, which is synchronised to the players via "Player Custom Properties".
-(No more complex algorithms are done here, just 10 points are assigned to the Master and 5 points to the other players).
-
-```javascript
-// Register new players to join the room event
-client.on(Event.PLAYER_ROOM_JOINED, (data) => {
- const { newPlayer } = data;
- console.log(`new player: ${newPlayer.userId}`);
- if (client.player.isMaster) {
- // Get a list of players in the room
- const playerList = client.room.playerList;
- for (let i = 0; i < playerList.length; i++) {
- const player = playerList[i];
- // Set 10 points if the judgement is a homeowner, otherwise set 5 points
- if (player.isMaster) {
- player.setCustomProperties({
- point: 10,
- });
- } else {
- player.setCustomProperties({
- point: 5,
- });
- }
- }
- // ...
- }
-});
-```
-
-Players get scores and display their scores.
-
-```javascript
-// Register for "Player Attribute Change" event
-client.on(Event.PLAYER_CUSTOM_PROPERTIES_CHANGED, (data) => {
- const { player } = data;
- // Deconstruct to get the player's score
- const { point } = player.customProperties;
- console.log(`${player.userId}: ${point}`);
- if (player.isLocal) {
- // Determine if the player is themselves and do the UI display
- this.scoreLabel.string = `score:${point}`;
- }
-});
-```
-
-## Communication via Custom Events
-
-When the score is distributed, the ID of the winner (Master) is sent as an argument to all players via a custom event.
-
-```javascript
-if (client.player.isMaster) {
- const WIN_EVENT_ID = 2;
- client.sendEvent(
- WIN_EVENT_ID,
- { winnerId: client.room.masterId },
- { receiverGroup: ReceiverGroup.All }
- );
-}
-```
-
-Make different UI displays depending on whether you judge the winner to be yourself or not.
-
-```javascript
-// Registering custom events
-client.on(Event.CUSTOM_EVENT, (event) => {
- // Deconstructing event parameters
- const { eventId, eventData } = event;
- if (eventId === "win") {
- // Deconstruction gets the winners Id
- const { winnerId } = eventData;
- console.log(`winnerId: ${winnerId}`);
- // If the winner is yourself, display the victory UI; otherwise display the defeat UI
- if (client.player.actorId === winnerId) {
- this.resultLabel.string = "win";
- } else {
- this.resultLabel.string = "lose";
- }
- client.close().then(() => {
- // Disconnect successful.
- });
- }
-});
-```
-
-## Demo
-
-We have completed this Demo in Cocos Creator, LayaAir, and Egret Wing for you to run and reference.
-
-[QuickStart Project](https://github.com/leancloud/Play-Quick-Start-JS)。
-
-## Build Notes
-
-You can build the projects supported by Cocos Creator.
-
-Only the Android project requires a little extra configuration when building it, which requires the following code to be added before initialising `play`:
-
-```js
-onLoad() {
- const { setAdapters } = Play;
- if (cc.sys.platform === cc.sys.ANDROID) {
- const caPath = cc.url.raw('resources/cacert.pem');
- setAdapters({
- WebSocket: (url) => new WebSocket(url, 'protobuf.1', caPath)
- });
- }
-}
-```
-
-The reason for this is that the SDK uses WebSocket-based wss for secure communication, and you need to adapt the CA certificate mechanism of Android platform through the above code.
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/push/guide/flutter.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/push/guide/flutter.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/push/guide/flutter.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/push/guide/flutter.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/push/guide/setup-flutter.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/push/guide/setup-flutter.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/push/guide/setup-flutter.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/push/guide/setup-flutter.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/_category_.json
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/_category_.json
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/_category_.json
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/faq.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/faq.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/faq.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/faq.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/guide.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/guide.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/guide.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/rest.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/rest.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/rest.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/sms/rest.mdx
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/_category_.json
deleted file mode 100644
index 7d914738b..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "入门指南",
- "collapsed": true,
- "position": 0
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/agreement.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/agreement.mdx
deleted file mode 100644
index ba9fd5064..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/agreement.mdx
+++ /dev/null
@@ -1,197 +0,0 @@
----
-title: TapSDK Privacy Policy
-sidebar_position: 8
----
-
-This Privacy Policy is intended to explain to developers and their end users the types of personal information we collect and how we handle and protect personal information. Before registering, accessing, and using TapSDK products and/or services, developers must read the Privacy Policy carefully and access and use it after confirming their full understanding and consent. If developers do not agree with the Privacy policy, they should immediately stop accessing and using TapSDK products and/or services.
-
-The Privacy Policy does not apply to the behavior of developers who access and use TapSDK products and/or services to process personal information of end users controlled by them in their apps, nor does it apply to services displaying, linking to or repackaging our products and/or services that apply third-party privacy policies and are provided by third parties. We recommend that end users carefully read, fully understand and agree to the privacy policy of the App/relevant third parties before using corresponding products/services.
-
-In order to facilitate reading and understanding by developers and end users, we have defined key terms, please refer to the Privacy Policy “Appendix: Definition of Key Terms”.
-
-**Special Instructions:**
-
-If the developer accesses and uses TapSDK products and/or services in its App, the developer is requested to know and promise:
-
-(1) The developer has complied with and will continue to comply with applicable laws, regulations, policies and regulatory requirements to collect, use and process personal information of end users and protect the security of personal information.
-
-(2) The developer has informed the end users about access and use of TapSDK products and/or services in his App, as well as the collection, use and protection rules of TapSDK on necessary personal information of the end users (i.e., the Privacy Policy), and has obtained sufficient, necessary and explicit authorization and consent from the end users on collection, use, and processing of their personal information by TapSDK (including obtaining authorization and consent from the child’s guardian to provide personal information of the end user who is a child).
-
-(3) The developer has provided the end users with an easy-to-operate user right realization mechanism, including, but not limited to, accessing, correcting, and deleting their personal information, revoking or changing the scope of their authorization and consent, and canceling their personal accounts.
-
-## I. How we collect and use personal information of the developers and/or end users
-
-### (I) Personal information we collect
-
-1. We will collect different information according to the different services the developer chooses:
-
-| Specific Functions/Service Scenarios | Collect Information/Obtain Permission |
-| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| [**TapTap Login**](https://developer.taptap.cn/docs/en/sdk/taptap-login/features/): Provide TapTap login method, players can quickly start the game through TapTap authorization. | Device version, mobile phone style, system version |
-| [**Data Analysis**](https://developer.taptap.cn/docs/en/sdk/tapdb/features/): Provide a set of analysis tools that focus on solving data requirements in game projects, which can obtain rich and practical data dashboards and advertising tracking capabilities through simple access, making data analysis and advertising easy to operate, and can also be used for analyzing user persona to help developers better understand users. | Read and write storage permissions, read phone status, IMSI, network device manufacturer, android ID, bssid device application list, WiFi information, device version, mobile phone model, system version |
-| [**Embedded Moments**](https://developer.taptap.cn/docs/en/sdk/embedded-moments/features/): Players can visit community forum of TapTap (official announcements, game guides, problem feedback, hot topics, etc.) within the game. They can also see the game dynamics of TapTap friends, and participate in interaction between other players, officials and great gods. | Read and write storage permissions, android ID, WiFi information, device version, system version |
-| [**Friends**](https://developer.taptap.cn/docs/en/sdk/friends/features/): Provide game developers with a complete functional interface for adding, deleting, and finding friends, helping games quickly form a social network. | WiFi information, system version |
-| [**Achievement**](https://developer.taptap.cn/docs/en/sdk/achievement/features/): You can set “Normal Achievements” and “Platinum Achievements” in the game to increase players’ participation in the game and encourage players to play the game in different ways. | System Version |
-| [**Anti-addiction**](https://developer.taptap.cn/docs/en/sdk/anti-addiction/features/): The fast real-name authentication function based on the TapTap account. For players who log in to the game with the TapTap account, after the player agrees to the authorization, the player is allowed to use real-name information that has been certified by the State in the TapTap to quickly complete the in-game authentication process. | IMSI, network equipment manufacturer, system version, read and write storage permissions |
-| [**Copyright Verification**](https://developer.taptap.cn/docs/en/sdk/lisence/features/): Help developers to verify whether paid games on players’ devices are paid for downloads through the TapTap store, effectively controlling scenarios where unpaid players get games from other sources. | Task List, System Version |
-| [**Real-time Communication (RTC)**](https://developer.taptap.cn/docs/en/sdk/rtc/features/): One-stop voice solution provides real-time voice, voice compliance services, covering FPS, MOBA, MMORPG, casual battle, online board games and other gameplay types. | Recording permission, WiFi information |
-
-2. When developers choose some of the TapTap platform services provided by TapSDK, or on the scenarios that the end users directly interact with the TapTap platform, please also abide by relevant provisions of privacy policy in the TapTap platform.
-
-3. We will collect and use personal information of the developers and/or end users in accordance with the purpose of collecting personal information stated in the Privacy Policy. If we want to use the collected personal information for other services/purposes not specified in the Privacy Policy, we will inform the developers and/or end users in a reasonable manner.
-
-4. The personal information/permission that TapSDK may collect depends on the selection of specific functions/services of TapSDK by the developer. If some apps do not cover certain services or do not provide specific functions, the content in the Privacy Policy involving the above services/functions and related personal information will not apply.
-
-### (II) Rules for the use of personal information
-
-1. We will process the collected personal information in accordance with the Privacy Policy and/or the agreement with the developers, and only for realizing functions of TapSDK products and/or services. If the collected personal information needs to be used for other purposes, we will inform the developers in a reasonable manner, and use it after the developers obtain consent from the end users.
-
-2. After collecting personal information from the developers and/or end users, we will de-identify or anonymize the personal information through technical means.
-
-3. After the TapSDK products and/or services we provide to the developers cease to operate, or we are reasonably notified that the developers and/or end users withdraw authorization of personal information, or we are reasonably notified that the developers and/or end users close the account or voluntary delete personal information, we will destroy, anonymize or de-identify all personal information received from the developers and/or end users within a reasonable period of time, unless otherwise required by law.
-
-### (III) Exception of prior authorization and consent
-
-1. Necessary for the personal information processor to perform legal obligations or legal duties;
-
-2. Necessary for conclusion and performance of the contract to which you are a party;
-
-3. Necessary to respond to public health emergencies, or to protect the life, health and property safety of natural persons in emergencies;
-
-4. To handle personal information already disclosed by yourself or other personal information that has been legally disclosed, within a reasonable scope in accordance with the law;
-
-5. Other circumstances stipulated by laws and administrative regulations.
-
-## II. How we use cookies and other similar technologies
-
-1. In order to ensure normal operation of the website, we will store small data file called Cookie on the end user’s computer or mobile device. Cookie usually contains identifier, site name, and some numbers and characters. The main function of Cookie is to facilitate end user to use website products and services, and to help website count the number of unique visitors. Using Cookie technology, we can provide the end user with more thoughtful personalized services, allow the end user to set their specific service options, and can manage or delete cookies according to his/her own preferences.
-
-2. When an end user uses products and/or services of TapSDK, we will send Cookie to the end user’s device. We allow Cookie, or other anonymous identifiers, to be sent to our server when an end user interacts with the products and/or services we provide to our partner. We will not use Cookie for any purpose other than those described in the Privacy Policy.
-
-## III. How we share, transfer, and publicly disclose personal information of the developers and/or end users
-
-### (I) Sharing
-
-1. We will share necessary personal information with our affiliates, which are bound by purposes mentioned in the Privacy Policy.
-
-2. We may share necessary personal information of the developers and/or end users with partners and third parties to ensure smooth completion of products and/or services provided to the developers and/or end users. Our partners are not authorized to use the shared personal information for any other purpose.
-
-3. For companies, organizations and individuals with whom we share personal information, we will investigate their data security environment and sign strict confidentiality agreements with them, requiring them to handle personal information in accordance with the same standards as ours.
-
-### (II) Transfer
-
-We will not transfer personal information of the developers and/or end users to any companies, organizations and individuals, except in the following cases:
-
-1. Obtain explicit authorization or consent from the developers and/or end users in advance;
-
-2. Meet requirements of laws & regulations and legal procedures, mandatory government requirements, or judicial rulings;
-
-3. When it comes to mergers, acquisitions, asset transfers, or similar transactions, if it involves transfer of personal information, we will require new companies and organizations that hold personal information of the developers and/or end users to continue to be bound by the Privacy Policy statement, otherwise, we will require the Companies or Organizations to seek authorization and consent from the developers and/or end users again.
-
-### (III) Public disclosure
-
-We will not publicly disclose personal information of the developers and/or end users, except in the following cases:
-
-1. Obtained explicit consent from the developers and/or end users;
-
-2. Mandatory requirements from laws, legal procedures, lawsuits, or government authorities.
-
-### (IV) Exceptions to prior authorization and consent when sharing, transferring, and publicly disclosing personal information
-
-In the following cases, the sharing, transfer, and public disclosure of personal information does not require prior authorization and consent from the subjects of personal information:
-
-1. Those directly related to national security and national defense security;
-
-2. Those directly related to criminal investigation, prosecution, trial and execution of judgments, etc.;
-
-3. Those related to the personal information controller’s performance of obligations under laws and regulations;
-
-4. The personal information disclosed by the subjects of personal information to the public on their own;
-
-5. The personal information collected from legally publicly disclosed information, such as legal news reports, government information disclosure, and other channels.
-
-## IV. How we store personal information of the developers and/or end users
-
-### (I) Storage period
-
-During use of TapSDK products and services by the developers and/or end users, we will continue to store personal information of the developers and/or end users. If the developers and/or end users close the account or voluntarily delete the above information, we will store personal information of the developers and/or end users within the time limit specified by relevant laws and regulations or within the time limit otherwise authorized and agreed by the subject of personal information.
-
-### (II) Storage location
-
-We will store in Chinese territory about personal information of the developers and/or end users obtained from the territory of China in accordance with Chinese laws and regulations. On the premise of not violating local laws and regulations, we will also store personal information of the developers and/or end users obtained locally in Chinese territory.
-
-## V. How we keep personal information of the developers and/or end users secure
-
-### (I) Security measures
-
-1. We will collect, use, store, and transmit user information in accordance with mature security standards and specifications in the industry, and use technical protection measures that comply with industry standards to protect personal information and personal sensitive information provided by developers and/or end users, including, but not limited to, firewalls, encryption, de-identification or anonymization, data desensitization encryption, access control measures, to prevent unauthorized access, public disclosure, use, modification, damage, loss, or leakage of data.
-
-2. We will conduct security background checks on the person in charge of security management and personnel in key security positions, and will also conduct identity authentication and authority control on employees who handle personal information, and sign confidentiality agreements with employees who have access to personal information, clarifying job duties and code of conduct, to ensure that only authorized personnel can access to personal information.
-
-3. We have established a dedicated team to ensure personal information security, and established special information security management system and internal handling mechanism for security incidents to prevent from security incidents, such as personal information leakage, damage, and loss.
-
-4. At present, TapTap has passed the network security level protection of the Ministry of Public Security of the People’s Republic of China, and the rating, filing and evaluation for the communication network unit rating and filing by the Ministry of Industry and Information Technology. We will try our best to protect security of your personal information.
-
-### (II) Handling mechanism for security incidents
-
-1. We will formulate emergency plan for security incidents about personal information, and regularly organize work team members to conduct drills about security emergency plan to prevent such security incidents from happening.
-
-2. In the event of security incidents, such as leakage, damage, and loss of personal information, we will promptly activate an emergency plan, taking corresponding measures to prevent expansion of the security incident, and report to relevant competent authorities in accordance with regulations.
-
-3. After security incidents about personal information occur, we will promptly notify the developers and/or end users about basic information of the security incident, the handling measures we have taken or will take, and our advice on how to respond to the subjects of personal information.
-
-## VI. How to manage personal information
-
-You can access and manage personal information in the following ways:
-
-1. Inquiry, correction, and supplement of personal information
-
-If the developers need to review, correct or supplement information that we store, please visit our website and log in to your account to operate. If exercise of certain right cannot be performed on the account page, you can contact us through the customer service system or contact information in the Policy, and we will assist you for corresponding operation.
-
-2. Account cancellation and deletion of personal information
-
-If the developers do not want to continue using our products, they can submit an application for account cancellation to us through the work order system (the specific path is to log in to the developer’s backstage account, click my customer service, and create a problem for feedback). We will respond to your application needs usually within 15 working days, but in order to ensure account security, we will require the developers to submit sufficient and valid identity information so that we can identify, verify, and process the request. After the account is cancelled, we will no longer provide services. If developers want us to delete their personal information, they can also submit an application for deletion of personal information to us.
-
-## VII. How we handle personal information of minors
-
-We attach great importance to the protection of minors’ personal information.
-
-Please ensure that the developer is over 18 years old, and understand and know:
-
-1. If the App is aimed at minor users under the age of 18 years old, and/or designed and developed for minor users under the age of 18 years old, the developer must ensure that the guardian of the end user (minor) has read and agreed to the privacy policy of the App, and has authorized and agreed to provide personal information of the minor to us in order to realize relevant functions described in the privacy policy of the App.
-
-2. If the App is aimed at children users under the age of 14 years old, and/or designed and developed for children users under the age of 14 years old, the developer must ensure that the guardian of the end user (child) has read and agreed to the privacy policy of the App and “TapTap Protection Rules for Children’s Personal Information”, and has authorized and agreed to provide personal information of the child to us in order to realize relevant functions described in the privacy policy of the App.
-
-We will only collect, use, and publicly disclose personal information of minors when permitted by law, with consent from parents or guardians, or necessary to protect minors. If we unknowingly or mistakenly collect personal information from minors, we will delete it promptly, unless we are required by law to retain such information. If we become aware that personal information of a minor has been collected without prior verifiable parental consent, we will take steps to delete relevant information as soon as possible.
-
-## VIII. Revision of privacy policy
-
-In order to provide developers with better services and with continuous development and changes of TapSDK products and/or services, we may revise the Statement in due course.
-
-When there are major changes to the Privacy Policy, we will announce to the developers and end users on the developer center page for developers and end users to view at any time; or send email or station mail to the developers to explain specific changes to the Privacy Policy, and indicate the effective date.
-
-If the developer does not agree to accept the revised Privacy Policy, please stop accessing and using our products and/or services; if the end user does not agree to accept the Privacy Policy, he/she can stop using TapSDK products and/or services. The developer shall provide corresponding implementation mechanism to the end user. If the end user continues to use it, it means that he/she agrees to be bound by the revised Privacy Policy.
-
-## IX. How to contact us
-
-If developers and/or end users have any questions about the Privacy Policy or matters related to personal information, please contact us in the following ways:
-
-(i) Send email to: ‘privacy@taptap.com’;
-
-(ii) Send postal mails to: North Building, B1, No. 718 Lingshi Road, Jing’an District, Shanghai, China Attn.: TapTap Legal Department Postal code: 200072.
-
-We will promptly verify identity of the applicant, review the issue involved as soon as possible, and respond within 15 days.
-
-### Appendix: Definition of key terms
-
-1. **We**: Refers to the corresponding enterprise entity that has the right to operate TapSDK in relevant area.
-
-2. **TapSDK**: It is a software development kit that provides mobile game data analysis services for mobile application developers (hereinafter referred to as “Developers”).
-
-3. **TapSDK products and/or services**: Refers to the SDK products and/or services developed by us and our affiliates.
-
-4. **Developers**: Refers to developer customers who register, access, and use TapSDK products and/or services.
-
-5. **End Users**: Refers to App end users who use embedded TapSDK products and/or services.
-
-6. **Personal Information**: Refers to various information recorded by electronic or other means that can identify the identity of a specific natural person or reflect activities of a specific natural person alone or in combination with other information. Personal information includes name, date of birth, ID number, personal biometric information, address, communication contact information, communication records and content, account and password, property information, credit information, whereabouts trajectory, accommodation information, health and physiological information, transactions information, etc.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/faq.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/faq.mdx
deleted file mode 100644
index 91bd96203..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/faq.mdx
+++ /dev/null
@@ -1,115 +0,0 @@
----
-title: Frequently Asked Questions
-sidebar_position: 6
----
-
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-import { Conditional } from "/src/docComponents/conditional";
-
-## Miscellaneous
-
-### Does TDS Cloud Services come with DDoS protection?
-
-TDS provides 2 Gbps of protection bandwidth by default, which protects against small attacks at no additional cost to the developer.
-For exclusive games, TDS provides high protection free of charge during the exclusive period. Developers simply need to communicate with TDS in advance about configuration details.
-For other games, TDS can assist in accessing the IaaS provider's DDoS protection service at the developer's expense (IaaS providers charge for DDoS protection bandwidth). Please contact us for further details.
-
-
-### Is there a limit to the number of accounts with real-name authentication for the same ID?
-
-Currently, TapTap requires that a single ID can only be used for real-name authentication on up to 5 TapTap accounts. There is no limit on real-name authentication for game accounts.
-
-
-### How do I get the MD5 of an Android application?
-
-#### 1. Using an app
-
-If you only have the APK file of your application, you can use the following tool to get the MD5: [**GenSignatureMD5**](https://capacity-files.lcfile.com/vW65JxH2b2KwDS8JcbVUfiwLHSeHTlD5/tds_getsign.apk). To use the tool, sign and package the game application with an official signature certificate, and then install the APK package on the phone. Install the GenSignatureMD5 tool on the same phone, then open the tool and enter the name of the game package to get the signature MD5.
-
-#### 2. Using Android Studio
-
-**For JDK 10 or below**, after signing the application correctly, open a terminal in the jks signature file directory and enter the command:
-
-```sh
-keytool -list -v -keystore xxx.jks
-```
-You will see the signature file information in the command window, including the SHA1 and MD5 values. **Note that this approach does not work for versions below JDK 10**.
-
-For versions above JDK 10, you can't get the MD5 using the above method, but you can use Android Studio's Gradle Tasks to check it, and the debug window will output the MD5 after double-clicking the signingReport in the figure below.
-
-![](https://capacity-files.lcfile.com/y7fcVDW6cUFKfG4ATDXj8KKE9L2jWprB/%E8%8E%B7%E5%8F%96MD5%E7%A4%BA%E4%BE%8B1.jpeg)
-
-:::caution
-
-Note that the MD5 value output from the signingReport debug window is separated by colons, so you will need to manually remove the colons when entering it into the Developer Center.
-Example of a value to enter in the TapTap Developer Center:
->
-> Correct: 6EB4347CF9C098BE1C8D965D539C42E2
->
-> Incorrect: 6E:B4:34:7C:F9:C0:98:BE:1C:8D:96:5D:53:9C:42:E2
-
-:::
-
-If there is no Gradle Tasks tab in the right Gradle panel, uncheck the options shown below in the settings and sync Gradle again to see the Gradle Tasks tab.
-
-![](https://capacity-files.lcfile.com/8dEVF81X34JFtUE50tnqj6OIoxxDdXsU/%E8%8E%B7%E5%8F%96MD5%E7%A4%BA%E4%BE%8B2.jpeg)
-
-
-### What should I do if the versions of okhttp used by TapSDK and Bilibili SDK on Android have conflicts?
-
-TapSDK now automatically includes the LeanCloud core SDK, and the LeanCloud SDK relies on the following base libraries:
-- com.squareup.okhttp3:okhttp:4.7.2
-- com.squareup.retrofit2:retrofit:2.9.0
-- io.reactivex.rxjava2:rxjava:2.2.19
-
-Some developers have given us feedback that the Bilibili game SDK is provided as an aar with okhttp 3.9.0 (at least until version 5.4.0), which conflicts with the TapSDK dependencies and causes the program to start with the following error:
-`Caused by: java.lang.NoSuchMethodError: No static method get(Ljava/lang/String;)Lokhttp3/HttpUrl; in class Lokhttp3/HttpUrl; or its super classes (declaration of 'okhttp3.HttpUrl' appears in /data/app/`
-
-Since the Bilibili game SDK fixes the okhttp version, solving this problem requires downgrading the okhttp, retrofit, and rxjava base libraries in TapSDK.
-You can copy the following configuration to the `dependencies` section of the app's build.gradle:
-
-
-
-Configuration of build.gradle
-
- {`
- // Note that the following code uses the latest version of the LeanCloud core SDK; if you are not using ${sdkVersions.leancloud.java}, please replace the version codes accordingly
- implementation('cn.leancloud:realtime-android:${sdkVersions.leancloud.java}'){
- exclude group: 'cn.leancloud', module: 'storage-android'
- exclude group: 'cn.leancloud', module: 'realtime-core'
- exclude group: 'cn.leancloud', module: 'storage-core'
- }
- implementation('cn.leancloud:storage-android:${sdkVersions.leancloud.java}'){
- exclude group: 'cn.leancloud', module: 'storage-core'
- }
- implementation('cn.leancloud:realtime-core:${sdkVersions.leancloud.java}') {
- exclude group: 'cn.leancloud', module: 'storage-core'
- }
- implementation('cn.leancloud:storage-core:${sdkVersions.leancloud.java}') {
- exclude group: 'com.squareup.okhttp3', module: 'okhttp'
- exclude group: 'com.squareup.retrofit2', module: 'retrofit'
- exclude group: 'com.squareup.retrofit2', module: 'adapter-rxjava2'
- exclude group: 'com.squareup.retrofit2', module: 'converter-gson'
- exclude group: 'io.reactivex.rxjava2', module: 'rxjava'
- }
- implementation("com.squareup.retrofit2:retrofit:2.3.0")
- implementation("com.squareup.retrofit2:adapter-rxjava2:2.3.0")
- implementation("com.squareup.retrofit2:converter-gson:2.3.0")
- implementation("io.reactivex.rxjava2:rxjava:2.0.0")
- implementation("com.google.code.gson:gson:2.8.6")
- implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'\n
- configurations {
- all*.exclude group: 'com.squareup.okhttp3'
- }
-`}
-
-
-
-### application id is empty
-
-Please ensure that the code for initializing the TapTap SDK is in the main thread to avoid this issue and subsequent functionality exceptions, such as not being able to access the network when logging in.
-
-### The TapTap Client will not notify in-game users of changes to their information.
-The TapTap client does not support synchronous update, but when the user successfully logs in through the client, it will return `access_token`, `mac_key`, and the game can locally cache the credentials.
-The game can cache the credentials locally, and then each time the user enters the game, it can call the [server-side authentication interface](/sdk/taptap-login/guide/taptap-oauth/) to make a query to synchronise the user's information.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/get-ready.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/get-ready.mdx
deleted file mode 100644
index 72de1aa86..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/get-ready.mdx
+++ /dev/null
@@ -1,47 +0,0 @@
----
-title: Before You Start
-sidebar_position: 3
----
-
-import { Conditional } from "/src/docComponents/conditional";
-import DomainBinding from "../_partials/setup-domain.mdx";
-
-Please complete the following setup process before you start using TapTap Developer Services (TDS).
-
-## Create an Application
-
-Before integrating TDS into your game, please first create an application. See [Store Guide](/store/) for more details.
-
-## Turn On Game Services
-
-Go to **TapTap Developer Center > Your Game > Game Services > Configuration** and tap “Turn on Now” to obtain the basic info of your app.
-
-### Basic Info
-
-`Client ID` is the unique identifier for an app package in the TapTap Developer Center. TapTap uses the `Client ID` to identify applications. Since each app has only one `Client ID`, if you need to have a test server besides an official server for the same app, you must create two different apps and activate their configurations separately.
-
-### Applicable Region
-
-
-
-TapTap has two service areas: one for Mainland China and one for other countries and regions. Each service area has its own account system and each application you create can only work for one of the two service areas.
-
-![](https://capacity-files.lcfile.com/nnQKxgJJzgErOlIOcxnbIHt8Vc1RmGYe/tap_get_ready.png)
-
-
-
-
-
-The service is applicable for countries and regions outside of Mainland China.
-
-![](https://capacity-files.lcfile.com/SaYP7m4TEQQTpuvy5n2r0GjxAzgim624/io_tap_get_ready.png)
-
-
-
-## Domains
-
-
-
-## Privacy Policy
-
-To integrate Account Services into your game, you must first agree to the [TapTap Platform Developers Agreement](/store/store-devagreement/). By using TDS, you agree to the above agreement. You will hereby bear the corresponding legal liabilities and obligations as per this agreement.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/overview.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/overview.mdx
deleted file mode 100644
index fd509e95d..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/overview.mdx
+++ /dev/null
@@ -1,76 +0,0 @@
----
-title: Overview
-slug: /sdk
-sidebar_position: 1
----
-
-import { Conditional } from "/src/docComponents/conditional";
-
-TapTap Developer Services (TDS) empowers game developers with a collection of tools that helps them develop, operate, and maintain quality games more efficiently than ever. Our vision is to promote a positive environment within the game industry for both developers and players.
-
-TDS provides the following services that you can use by integrating TapSDK in your games:
-
-- **[TapTap Login](/sdk/taptap-login/features/)**: Allows players to quickly log into the game by authorizing with a TapTap account.
-
-- **[TapTap Connect](/sdk/taptap-connect/features/)**: A tool that connects TapTap, the game, and the player.
-
-- **[Data Analysis](/sdk/tapdb/features/)**: A complete set of analytic tools focused on solving data needs of game projects. Simple integration allows you to obtain an in-depth and practical data dashboard and ad-tracking capabilities, making data analysis and advertising easy to conduct. Additionally, it can also be used to perform crowd analysis to help you better understand users.
-
-- **[Moments](/sdk/embedded-moments/features/)**: Allows players to access TapTap’s community forums (official announcements, game strategies, problem feedback, trending topics, etc.) without leaving the game. Additionally, they can view their friends’ game moments and interact with their friends as well as game developers and top players.
-
-
-
-- **[TapTap Friends](/sdk/tap-friend/features/)**: Player can access a list of TapTap friends who are playing the same game by logging into their TapTap account.
-
-
-
-- **[TDS Authentication](/sdk/authentication/features/)**: Helps you quickly build a safe and secure player login system at a low cost, providing the capability for players to log into your game with a variety of accounts including guest accounts and third-party accounts (TapTap, Apple, WeChat, QQ, Facebook, etc.).
-
-
-
-- **[Achievements](/sdk/achievement/features/)**: Allows you to set up “Achievements” and “Platinum Achievements” to increase player participation and encourage players to explore different ways to play the game.
-
-
-
-- **[Leaderboards](/sdk/leaderboard/features/)**: Based on TDS Authentication, you can set up leaderboards in your game to promote competition among players and thereby increase player activity.
-
-- **[Cloud Save](/sdk/gamesaves/features/)**: Saves the player’s game progress to the TDS server, where the game can retrieve saved game data and allow the player to continue playing from any save point on any device.
-
-- **[Gift Packages](/sdk/tds-gift/features/)**: Gift Packages lets you create gift codes for your game which players can redeem in your game later.
-
-
-
-- **[TapLink](/sdk/taplink/features/)**: Helps players jump from TapTap to the game to collect the gift packs.
-
-- **[Anti Addiction](/sdk/anti-addiction/features/)**: A fast real-name authentication feature based on TapTap accounts. With players’ permission, those who log in to the game with a TapTap account can quickly complete the real-name authentication process with their already-verified personal information filed with TapTap.
-
-
-
-- **[Copyright Verification](/sdk/copyright-verification/features/)**: Helps buyout games verify paid download eligibility and DLC unlock eligibility.
-
-- **[Updates](/sdk/update/guide/)**: Whenever the game has an update, players can jump directly from the game to TapTap and download the update.
-
-
-
-- **[TapPlay](/sdk/tap-play/features/)**: Run your games in sandboxes so you can develop your games more efficiently and let your games reach more players.
-
-- **[TapCanary](/sdk/tap-canary/features/)**: Release early versions of your games to internal testers or trusted users for closed testing with either cloud play or TapPlay (sandbox).
-
-
-
-- **[Data Storage](/sdk/storage/features/)**: Store and retrieve JSON objects, binary files, geolocations, and other types of data. Its built-in row-level ACL permission control and general user and role management system can help you quickly achieve safe and flexible data access.
-
-- **[Cloud Engine](/sdk/engine/overview/)**: Provides an exclusive cloud computing platform for hosting static websites. Additionally, it allows customized development using any programming language to dynamically process external requests and meet the needs of business customization. This eliminates the need to build your own server for back-end development.
-
-- **[Voice Chat](/sdk/rtc/features/)**: Provides a one-stop-shop for voice chat and voice compliance solutions, covering FPS, MOBA, MMORPG, matchmaking, online board games, and various other gaming genres.
-
-
-
-
-- **[Multiplayer](/sdk/multiplayer/features/)**: Rely on cloud services for easy in-game player matching, online matchmaking message synchronisation and more.
-
-
-
-- **[Customer Support](/sdk/tap-support/features/)**: Helps the game's operation team better solve the problems encountered by players.
-
-To use the corresponding services, complete [Developer Registration](https://developer.taptap.cn/)[Developer Registration](https://developer.taptap.io/), log into the Developer Center, and “Turn on” Game Services.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/quickstart.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/quickstart.mdx
deleted file mode 100644
index 53df3fd2b..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/quickstart.mdx
+++ /dev/null
@@ -1,435 +0,0 @@
----
-title: TapSDK Quickstart
-sidebar_label: Quickstart
-sidebar_position: 4
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-import { Conditional } from "/src/docComponents/conditional";
-
-This article will introduce how to quickly integrate TapSDK into your app and use the **[TapTap Login](/sdk/taptap-login/guide/start/)** function.
-
-:::note
-
-The [Downloads](/tap-download) page provides Unity, Android, and iOS demos for your reference.
-
-:::
-
-## Creating a Game
-
-Log into the [TapTap Developer Center](https://developer.taptap.cn/)[TapTap Developer Center](https://developer.taptap.io/) to register as a developer and create a game.
-
-## Downloading the TapTap App
-
-Download the [TapTap app](https://www.taptap.cn/mobile)[TapTap app](https://www.taptap.io/mobile) on your test device. When testing your game, the SDK will take you to the TapTap app for the authorization process. If the TapTap app is not installed on your device, a WebView will be opened for you to log in.
-
-## Environment Requirements
-
-
-<>
-
-- Unity 2019.4 or later
-- iOS 11 or later, Xcode 14.1 or later
-- Android 5.0 (API level 21) or higher
-
->
-<>
-
-- Android 5.0 (API level 21) or higher
-
->
-<>
-
-- iOS 11 or later, Xcode 14.1 or later
-
->
-
-
-:::caution
-
-- In the **Project Configurations** and **Initialization** sections below, we assume that you will use [TDS Authentication](/sdk/authentication/features/).
-- If the game already has a complete account system and only needs TapTap Login and Moments without additional TDS cloud services, there is no need for you to complete the configurations and initializations below. You can jump to [Basic TapTap Login Developer Guide](/sdk/taptap-login/guide/tap-login/) and [Moments Developer Guide](/sdk/embedded-moments/guide/).
-- Please make your decision carefully. If you need other TDS services at a later time, it could be hard for you to make a switch.
-
-:::
-
-## Project Configuration
-
-
-<>
-
-The SDK can be imported **either using the Unity Package Manager or manually**.
-
-#### Method 1: Use Unity Package Manager
-
-Add the following dependencies into `Packages/manifest.json`:
-
-
- {`"dependencies":{
- "com.taptap.tds.login":"https://github.com/TapTap/TapLogin-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.common":"https://github.com/TapTap/TapCommon-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.bootstrap":"https://github.com/TapTap/TapBootstrap-Unity.git#${sdkVersions.taptap.unity}",
- "com.leancloud.realtime": "https://github.com/leancloud/csharp-sdk-upm.git#realtime-${sdkVersions.leancloud.csharp}",
- "com.leancloud.storage": "https://github.com/leancloud/csharp-sdk-upm.git#storage-${sdkVersions.leancloud.csharp}",
-}`}
-
-
-In Unity’s menu bar, select **Window > Package Manager** to view the packages already installed in the project.
-
-#### Method 2: Import Manually
-
-1. Open the download pages of **TapSDK Unity** and **LeanCloud C# SDK** from the [Downloads](/tap-download) page and download `TapSDK-UnityPackage.zip` and `LeanCloud-SDK-Realtime-Unity.zip` from these pages.
-
-2. Go to your Unity project, navigate to **Assets > Import Packages > Custom Packages**, and select the TapSDK packages you want to use from the unzipped `TapSDK-UnityPackage.zip` file.
-
- - `TapTap_Bootstrap.unitypackage` is the TapSDK Launcher (required).
- - `TapTap_Common.unitypackage` is the TapSDK Basic Library (required).
- - `TapTap_Login.unitypackage` is for TapTap Login (required).
-
-3. Drag the unzipped `LeanCloud-SDK-Realtime-Unity.zip` (the Plugins folder) into Unity.
-
-:::tip
-
-If you have manually downloaded the `unitypackage` to import the SDK, please edit the configuration file `Assets/TapTap/Common/Plugins/iOS/TapTap.Common.dll` to support iOS only.
-
-:::
-
-After importing the SDK, please continue with the following instructions to get your project ready for iOS.
-
-#### iOS Configuration
-
-Create a file named `TDS-Info.plist` under the `Assets/Plugins/iOS/Resource` directory and paste the following code into the file with `ClientId` replaced with your game’s Client ID. If you plan to use [Embedded Moments](/sdk/embedded-moments/features/) or [Data Analysis](/sdk/tapdb/features/), make sure to update the permission configurations accordingly, as well as the **text for the permission dialogue**:
-
-```xml
-
-
-
-
- taptap
-
- client_id
- ClientId
-
-
-
- NSPhotoLibraryUsageDescription
- Explain why your app requires this permission.
- NSCameraUsageDescription
- Explain why your app requires this permission.
- NSMicrophoneUsageDescription
- Explain why your app requires this permission.
-
- NSUserTrackingUsageDescription
- Explain why your app requires this permission.
-
-
-```
-
->
-<>
-
-1. [Download TapSDK Android](/tap-download), unzip it, and import the packages you need to `project/app/libs`.
-
-2. Open the `project/app/build.gradle` file under your project and add the following gradle configurations:
-
- {`
-dependencies {
- ...
- // Import all aar packages in the libs directory:
- implementation fileTree(dir: 'libs', include: ['*.aar'])
- // Or import the specified packages in the libs directory:
- //implementation files('libs/TapBootstrap_${sdkVersions.taptap.android}.aar')
- //implementation files('libs/TapCommon_${sdkVersions.taptap.android}.aar')
- //implementation files('libs/TapLogin_${sdkVersions.taptap.android}.aar')
- ...
- // Data Storage
- implementation 'cn.leancloud:storage-android:${sdkVersions.leancloud.java}'
- // Instant Messaging
- implementation 'cn.leancloud:realtime-android:${sdkVersions.leancloud.java}'
- implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
-}
-`}
-
-3. Add the network permission to `AndroidManifest.xml`:
-
- ```java
-
- ```
-
-4. Add additional configurations for older Android versions.
-
- If `targetSdkVersion < 29`, make sure to:
-
- - Add `xmlns:tools="http://schemas.android.com/tools"` into `manifest`
- - Add `tools:remove="android:requestLegacyExternalStorage"` into `application`
-
->
-<>
-
-#### Import the SDK
-
-1. Select the project in Xcode, add `-ObjC` and `-Wl -ld_classic` into **Build Setting > Other Linker Flags**.
-
-2. Download [TapSDK iOS](/tap-download), unzip it, and drag and drop the resources you need into the project directory.
-
-3. Import the downloaded resource files as needed:
-
- - Required: TapTap Launcher, Basic Library, and TapTap Login
-
- ```
- TapBootstrapSDK.framework
- TapCommonSDK.framework
- TapLoginSDK.framework
- LeanCloudObjc.framework
- TapCommonResource.bundle
- TapLoginResource.bundle
- ```
-
-4. Please carefully check whether the following dependency libraries are added successfully:
-
- ```
- // Required
- WebKit.framework
- Security.framework
- SystemConfiguration.framework
- CoreTelephony.framework
- SystemConfiguration.framework
- libc++.tbd
-
- // TapTap Moments
- AVFoundation.framework
- CoreTelephony.framework
- MobileCoreServices.framework
- Photos.framework
- SystemConfiguration.framework
- WebKit.framework
-
- // Data Analysis
- // If you do not need access to the IDFA, please don’t add the system libraries `AppTrackingTransparency` and `AdSupport`.
- AppTrackingTransparency.framework
- AdSupport.framework
- CoreMotion.framework
- Security.framework
- SystemConfiguration.framework
- libresolv.tbd
- libsqlite3.0.tbd
- libz.tbd
- ```
-
-#### Configure Permissions
-
-If you plan to use TapTap Moments or Data Analysis in your game, please add the following configurations to `info.plist` and **update the text for the permission dialogue**:
-
-```xml
-
-NSPhotoLibraryUsageDescription
-Explain why your app requires this permission.
-NSCameraUsageDescription
-Explain why your app requires this permission.
-NSMicrophoneUsageDescription
-Explain why your app requires this permission.
-
-NSUserTrackingUsageDescription
-Explain why your app requires this permission.
-```
-
-#### Launching the TapTap App From Your Game
-
-If a user does not have the TapTap app on their device, a WebView will be displayed for them to log in.
-
-1. Open `info.plist` and add the following configurations (replace `clientID` with the Client ID you obtained from the Developer Center):
-
- ![](https://capacity-files.lcfile.com/xLmohBqQCMvHMpvxps7pReT9kI8CjSGj/tap_ios_info.png)
-
- ```xml
- CFBundleURLTypes
-
-
- CFBundleTypeRole
- Editor
- CFBundleURLName
- taptap
- CFBundleURLSchemes
-
-
- tt[clientID]
-
-
-
-
- LSApplicationQueriesSchemes
-
- tapiosdk
- tapsdk
- taptap
-
- ```
-
-2. Configure openUrl:
-
- a) If the project has `SceneDelegate.m`, please first delete it and then add the following code into the `AppDelegate.m` file:
-
- ```objectivec
- - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
- return [TDSHandleUrl handleOpenURL:url];
- }
-
- - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
- return [TDSHandleUrl handleOpenURL:url];
- }
- ```
-
- b) Delete the Application Scene Manifest from `info.plist`.
-
- ![](/img/tap_ios_appmanifest.png)
-
- c) Delete the two lifecycle delegate methods for managing `Scenedelegate` from `AppDelegate.m`.
-
- ```objectivec
- #pragma mark - UISceneSession lifecycle
- - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
-
- return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
- }
-
- - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions {
- }
- ```
-
- d) Add `UIWindow` to `AppDelegate.h`.
-
- ```objectivec
- @property (strong, nonatomic) UIWindow *window;
- ```
-
->
-
-
-## Initialization
-
-When initializing the TapSDK, please provide the application information including the `Client ID` and the region.
-
-
-
-<>
-
-```cs
-using TapTap.Bootstrap; // Namespace
-using TapTap.Common; // Namespace
-
-var config = new TapConfig.Builder()
- .ClientID("your_client_id") // (Required) Corresponding Client ID in the Developer Center
- .ClientToken("your_client_token") // (Required) Corresponding Client Token in the Developer Center
- .ServerURL("https://your_server_url") // (Required) Developer Center > Your Game > Game Services > Configuration > Domain > API
- .RegionType(RegionType.CN) // (Optional) CN for Mainland China; IO for international
- .ConfigBuilder();
-TapBootstrap.Init(config);
-```
-
->
-<>
-
-**Make sure to execute the code on the UI thread**:
-
-```java
-TapConfig tdsConfig = new TapConfig.Builder()
- .withAppContext(MainActivity.this) // Context
- .withClientId("your_client_id") // (Required) Corresponding Client ID in the Developer Center
- .withClientToken("your_client_token") // (Required) Corresponding Client Token in the Developer Center
- .withServerURL("https://your_server_url") // (Required) Developer Center > Your Game > Game Services > Configuration > Domain > API
- .withRegionType(TapRegionType.CN) // TapRegionType.CN for Mainland China; TapRegionType.IO for International
- .build();
-TapBootstrap.init(MainActivity.this, tdsConfig);
-```
-
->
-<>
-
-```objectivec
-// Import `TapBootstrap`, `TapLogin`, `TapCommon`, and `LeanCloudObjc`, and then initialize the SDK:
-TapConfig *config = [TapConfig new];
-config.clientId = @"your_client_id"; // (Required) Corresponding Client ID in the Developer Center
-config.clientToken = @"your_client_token"; // (Required) Corresponding Client Token in the Developer Center
-config.serverURL = @"https://your_server_url"; // (Required) Developer Center > Your Game > Game Services > Configuration > Domain > API
-config.region = TapSDKRegionTypeCN; // TapSDKRegionTypeCN for Mainland China; TapSDKRegionTypeIO for International
-[TapBootstrap initWithConfig:config];
-```
-
->
-
-
-
-`client_id`, `client_token`, and `server_url` are required when you initialize the SDK.
-
-- You can find `client_id` and `client_token` on **Developer Center > Your Game > Game Services > Configuration**.
-
-- Please **use the HTTPS protocol** for `server_url`. See **[Bind Domain Name](/sdk/start/get-ready/#domain-names)** to learn more about `server_url`.
-
-## Integrate Functions
-
-TapSDK provides a variety of functions. After initializing the SDK, feel free to browse the other docs on this site to learn how to enable those functions.
-One of our popular functions is TapTap Login. We recommend this as the starting point.
-
-### Integrate TapTap Login
-
-Follow the Developer Guide [Getting Started, Integrate TapTap Quick Login](/sdk/taptap-login/guide/start) to finish setting up.
-
-### Configure Signature Certificate
-
-For Android and iOS applications, go to your game from the Developer Center, then go to **Game Services > Gaming Ecosystem > TapTap Login** to configure the corresponding app information (see below). Otherwise, the SDK will return a `signature not match` error, and the TapTap Login function will not work properly.
-
-Fill in the MD5 hash in the "Android Signature" field. See **[How to get MD5 hash](/sdk/start/faq/#如何获取-android-应用的-md5-值)** to learn more.
-
-
-
-![](https://capacity-files.lcfile.com/YmcgwIbUzC7dRKunBT5jQ1lYEO2hedWG/start_getready_info.png)
-
-
-
-
-
-![](https://capacity-files.lcfile.com/MM13UMrcN5n1WSJyClE7QQHb5f9ue4o6/io-login-config.png)
-
-
-
-
-Next, you can package the application and test the TapTap Login function.
-
-### Android Code Obfuscation
-
-TapSDK has already undergone obfuscation. Therefore, initiating another obfuscation will lead to unexpected issues. Please add the following configurations in the obfuscation scripts to skip TapSDK obfuscation:
-
-```java
--keep class com.tds.** { *;}
--keep class com.taptap.** { *;}
--keep class com.tapsdk.** { *;}
--keep class tds.androidx.** { *;}
-```
-
-If you plan to use any cloud services based on the **Data Storage** service (e.g. logging in with **TDS Authentication**), please also add the obfuscation code for **[Data Storage](/sdk/storage/guide/setup-java/#android-code-obfuscation)**.
-
-## Package
-
-For Android and iOS apps, you can follow the ordinary packaging process. The following instruction introduces the packaging process for Unity:
-
-### Package the APK
-
-Step 1: Configure package name and signature file:
-
-![](https://capacity-files.lcfile.com/qooIRbr5qtLrnhsP0hWjOSnBYW12eNg6/tap_unity_android_build.png)
-
-Step 2: Inspect **File > Build Settings > Player Settings > Other Settings > Target API Level**. If the API Level is below 29, please configure the manifest by adding the following to the `application` node:
-
-```
-tools:remove="android:requestLegacyExternalStorage"
-```
-
-This is because the SDK is configured with `android:requestLegacyExternalStorage = true` by default. If `targetSdkVersion < 29`, the SDK will throw the error `Android resource linking failed`.
-
-### Export Xcode Program
-
-Please configure the icon and `BundleID`:
-
-![](https://capacity-files.lcfile.com/Nke4QO6zdEz5mRd2Kwd8R9ydyP8QYaJy/tap_ios_build.png)
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/ver-lifetime.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/ver-lifetime.mdx
deleted file mode 100644
index 3df6dd07e..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/ver-lifetime.mdx
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title: TapSDK Versions and Maintenance Cycle
-sidebar_position: 9
----
-
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-import { Conditional } from "/src/docComponents/conditional";
-
-## Rules for versioning
-
-TapSDK is the generic name for many of the services we provide. To make it easier for developers to access them on demand, we use a separate module for each sub-service. For example: `TapTap Login` corresponds to the `TapLogin` module, `Billboard` corresponds to the `TapBillboard` module, and so on.
-
-We use [semantic versioning](https://semver.org/) to plan versions for TapSDK. The version is formatted mainly with three parts: `MAJOR.MINOR.PATCH`, where:
-
-- MAJOR: Indicates a collection of features and API interfaces. If there are incompatible changes introduced in an update, we will upgrade the MAJOR version number;
-- MINOR: When there are functional additions that ensure backward compatibility, the MINOR version number will be increased. For instance, when a new customer service module is added to version 3.4.0, the updated version will be 3.5.0;
-- PATCH: If we fix a bug in the SDK or optimize its internal implementation while keeping the external interface unchanged, we will upgrade the PATCH version number;
-- When there are pre-released versions or versions released due to platform-specific limitations, we sometimes append the information to "MAJOR.MINOR.PATCH" as an extension.
-
-Currently the main line of our development is the **3.x version**. For the SDK versions and changelist for each platform, please refer to the following links:
-
-- Unity Engine SDK: https://github.com/taptap/TapSDK-Unity/releases
-- Unreal Engine SDK:https://github.com/taptap/TapSDK-UE4/releases
-- Android SDK:https://github.com/taptap/TapSDK-Android/releases
-- iOS SDK:https://github.com/taptap/TapSDK-iOS/releases
-
-## Lifecycle of version maintenance
-
-Each major release is **continuously maintained** if it is a mainline release that we develop/maintain - this is the case for the current 3.x version of the SDK**. If it's not a major release, we **maintain it for two years** after we stop developing new features. If the latest version is 3.16.5, and in the next iteration we change the external interfaces and move to 4.x, then we will maintain 3.x for two years from the time 4.0.0 is released - there will only be bug fixes and no new features; and then after two years 3.x will be completely deprecated.
-
-For minor versions under the current mainline (e.g. 3.1.x/3.2.x), if a bug is found, we will fix it in the current latest version (not in the minor version where the bug was found). Since the interfaces are fully compatible between minor version numbers and the sub-functions are mostly released as independent modules (no impact between modules), developers **can upgrade to the latest minor version without any burden**.
-
-Let's look at an example. A developer uses real name authentication and anti-addiction by integrating the `TapLogin/BootStrap/AntiAddiction` modules from the version `3.5.3`, while the latest version is currently `3.10.4`. Later:
-
-- The developer found and reported a new bug in the SDK. We followed up to fix it and released version `3.10.5`. The developer can directly upgrade the corresponding modules from `3.5.3` to `3.10.5`;
-- If the developer found and reported a bug that has already been fixed (e.g. no longer exists since `3.8.0`), then the developer can simply upgrade to our latest version, `3.10.4`.
-
-We recommend developers to update TapSDK regularly and keep a reasonable update frequency to ensure the stability of the client and better user experience. Meanwhile, we will publish the SDK update plan in advance of any interface incompatibility update; we will also remind developers to upgrade the SDK that will be out of maintenance cycle in time. The update plan and upgrade notification will be sent to all developers via email. After receiving the email notification, developers who need to upgrade their SDKs must do so as soon as possible to avoid any impact on functionality or revenue.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/version-releases.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/version-releases.mdx
deleted file mode 100644
index 47408c427..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/start/version-releases.mdx
+++ /dev/null
@@ -1,659 +0,0 @@
----
-title: TapSDK Release Notes
-sidebar_position: 7
----
-
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-import { Conditional } from "/src/docComponents/conditional";
-
-## TapSDK V-3.19.1
-Released 2023-07-19
-
-### Bug fixes
-- **TapDB:** Fixed a bug in PC platform decoding cached data
-
-### Improvements
-- **Billboard:** Optimized the blurring of the PC announcement panel (increased WebView rendering density)
-
-### Internal changes
-- **Login:** Replace the domain name for web page authorization.
-
-## TapSDK V-3.19.0
-Released 2023-07-07
-
-### Features
-- **TapPayment:** Supports WeChat and Alipay App Payment and WeChat Payment, depends on TapCommon version 3.19.0.
-
-### Bug fixes
-- **AntiAddiction:** Fix the issue that the ID input box in the manual real name window cannot input X on some devices.
-
-### Internal changes
-- **Common:** Added support for TapPayment interface.
-
-## TapSDK V-3.18.9
-Released 2023-06-30
-
-### Improvements
-- **AntiAddiction:** To optimise the issue of multiple authorisations due to multiple clicks using Tap Authentication, add a Loading prompt when authorisation is triggered.
-
-### Features
-- **PaymentsGlobal:** Added an overseas payments module to provide developers with the best solution for selling games on the TapTap shop and selling digital goods and content within games.
-
-## TapSDK V-3.18.8
-Released 2023-06-15
-
-### Bug fixes
-- **Login:** Fixes a login failure message that appears briefly in the login window during TapTap login.
-
-### Improvements
-- **DB:** Android adds a catch for an exception caused by OAID missing a so library file for the platform architecture.
-- **AntiAddiction:** Android changed the existing Lambda part to a normal anonymous class for compatibility with Java 1.8 and Gradle plugins below 4.0 that don't support Lambda expressions.
-- **Friend:** Android for compatibility with Java 1.8 and Gradle plugin below 4.0 does not support Lambda expression issue, change the existing Lambda part to normal anonymous class.
-- **Login:** PC now supports Indonesian, Japanese, Korean, Thai, and Traditional Chinese.
-
-### Internal changes
-- **Common:** Android now supports to get clientToken and serverUrl fields in TapConfig via ContentProvider after TapSDK initialisation.
-
-
-## TapSDK V-3.18.7
-Released 2023-05-25
-
-### Bug fixes
-- **AntiAddiction:** Fixed the issue that anti-addiction window is not fully displayed on some Android models.
-
-### Improvements
-- **DB:** Support new CAID interface.
-
-
-## TapSDK V-3.18.6
-Released 2023-04-27
-
-### Bug fixes
-- **AntiAddiction:** Fix the issue that the page can't disappear after cancelling the authorisation in some cases.
-
-### Improvements
-- **Support:** Multi-language support for open customer service page.
-- **Achievement:** Extend network timeout to 10s.
-- **Achievement:** Optimise the parsing of some error messages.
-- **DB:** Optimise the number of reads and writes to the cache file.
-- **Moment:** Optimise rotation logic
-- **Common:** Remove unnecessary architectural support in LZ4.
-
-
-## TapSDK V-3.18.5
-Released 2023-04-10
-
-### Features
-- **Common:** Add application configuration data encapsulation.
-- **DB:** TapDB adds device properties.
-- **DB:** Add device identification data encapsulation.
-- **Login:** Adds user information data encapsulation.
-
-
-## TapSDK V-3.18.5-1
-Released 2023-04-20
-
-### Improvements
-- **DB:** Optimise language settings
-
-
-## TapSDK V-3.18.4
-Released 2023-03-30
-
-### Features
-- **DB:** PC interface rewrite
-- **DB:** TapDB new interface to control whether to use Themis or not.
-- **DB:** Themis update
-- **License:** New interface supports skip cache to force checking purchase status.
-
-### Bug fixes
-- **DB:** TapDB no longer obtains ipv6 address
-- **DB:** TapDB version number changed to TapSDK version number.
-
-
-## TapSDK V-3.18.3
-Released 2023-03-23
-
-### Bug fixes
-- **AntiAddiction:** Fix iOS no callback exception.
-
-
-## TapSDK V-3.18.2
-Released 2023-03-20
-
-### Features
-- **AntiAddiction:** Update anti-addiction authentication interface.
-- **Achievement:** UI adapted to large screen devices.
-- **Achievement:** Support for PC Version
-
-### Bug fixes
-
-- **AntiAddiction:** Fix UI misalignment of alerts
-- **AntiAddiction:** Fixed iOS curfew not popping up in some cases.
-- **DB:** Disable getting device id from TapTap by default.
-
-
-## TapSDK V-3.18.1
-Released 2023-03-06
-
-### Features
-- **DB:** Add network redirection support.
-
-### Bug fixes
-- **Payment:** Fixed the exception that there is no callback when user payment is cancelled voluntarily.
-- **Support:** Fix the problem of white screen when clicking retry when disconnecting and reconnecting.
-- **Billboard:** Fix a configuration issue with Unity's packaging.
-- **Billboard:** Fix an exception in the initialisation interface.
-
-
-## TapSDK V-3.18.0
-Released 2023-02-20
-
-### Features
-- **Support:** Support own account related login.
-- **Support:** Support TDS built-in account login.
-- **Support:** Optimise customer service interface.
-
-
-## TapSDK V-3.17.0
-Released 2023-02-17
-
-### Features
-- **Billboard:** Add Billboard and Open Screen Announcement.
-
-### Improvements
-- **DB:** Optimise device compatibility.
-
-
-## TapSDK V-3.16.6
-Released 2023-01-06
-
-### Bug fixes
-- **AntiAddiction:** Fix the bug that the game may restart when quitting the game under Unity Android multi-activity.
-
-
-## TapSDK V-3.16.5
-Released 2022-12-05
-
-### Features
-- **DB:** Add device time reporting.
-
-### Improvements
-- **AntiAddiction:** Add platform compatibility handling.
-- **Payment:** Optimises the device ID fetching process.
-
-
-## TapSDK V-3.16.4
-Released 2022-11-17
-
-### Features
-- **DB:** Update reported domain name and data format.
-- **Login:** Modify web login UI display.
-- **AntiAddiction:** Add optimisation of real name process in sandbox.
-
-### Bug fixes
-- **Login:** Fix the exception of eligibility verification callback in some cases.
-- **Bootstrap:** Fix push service triggered when device starts or network status is updated.
-
-
-## TapSDK V-3.16.2
-Released 2022-11-01
-
-### Features
-- **Support:** Added ability to open customer service page in SDK.
-- **DB:** CAID attribution support for iOS.
-
-
-## TapSDK V-3.16.1
-Released 2022-10-17
-
-Changelog
-
-
-### Features
-- **Billboard:** Add close button event callback.
-- **Billboard:** Optimise UI display when network exception occurs.
-- **Friend:** Add support for buried data.
-- **DB:** Support interface setting OAID application package key.
-
-### Bug fixes
-- **Billboard:** Fix iOS 16 compatibility issue.
-
-
-## TapSDK V-3.16.0
-Released 2022-09-30
-
-### Features
-- **Payment:** Support overseas payment.
-
-### Bug fixes
-- **AntiAddiction:** Fix upm import error.
-
-### Improvements
-- **DB:** TapDB iOS supports Mac with Arm chip.
-
-
-## TapSDK V-3.15.1
-Released 2022-09-15
-
-### Features
-- **Common:** Add UI module for anti-addiction dependency.
-
-### Bug fixes
-- **AntiAddiction:** Fix the compilation error caused by missing UI module (UI module has been moved to Common library).
-
-
-## TapSDK V-3.15.0
-Released 2022-09-13
-
-Warning: This version is anti-sedimentation, please don't use this version yet.
-
-### Bug fixes
-- **Achievement:** Fix Achievement Android SDK failed to initialise for the first time on Android 12.
-
-### Improvements
-- **AntiAddiction:** Anti-Addiction interface optimisation.
-- **DB:** Adaptation of new OAID for Android SDK.
-- **Login:** Optimise unnecessary error logs in Android SDK.
-
-
-## TapSDK V-3.14.0
-Released 2022-08-25
-
-### Features
-- **Themis:** New exception reporting module.
-
-### Bug fixes
-- **DB:** TapDB adds Themis support.
-
-
-## TapSDK V-3.13.0
-Released 2022-08-18
-
-### Features
-- **Login:** iOS and Android Tap login support for new scopes (basic_info & email)
-- **Login:** iOS and Android Tap login support new scope (basic_info & email).
-
-### Bug fixes
-- **Login:** Android Tap login fixes some special case crashes (added null check to avoid NPE).
-
-
-## TapSDK V-3.12.1
-Released 2022-08-09
-
-### Bug fixes
-- **Common:** Fix NPE generated by not passing announcement parameter during TapSDK initialisation
-
-
-## TapSDK V-3.12.0
-Released 2022-08-04
-
-Danger: TapBootstrap generates a null pointer exception when initialising, please don't use this version yet.
-
-
-### Features
-- **Billboard:** New Announcement module.
-
-### Bug fixes
-- **Payment:** Fix a crash problem when the payment window is rotated.
-
-
-## TapSDK V-3.11.1
-Released 2022-07-25
-
-### Bug fixes
-- **Login:** Fix iOS login module's usage of system URL callbacks.
-- **Common:** Fix iOS Authorisation module's usage of system URL callbacks.
-
-### Improvements
-- **Friend:** Update the way to get Tap friends.
-
-
-## TapSDK V-3.11.0
-Released 2022-07-12
-
-### Bug fixes
-- **Achievement:** Fixed a parsing problem in the bridge section.
-- **DB:** Compatibility handling for bridge incoming parameter null.
-- **DB:** Interface optimisation
-- **AntiAddiction:** iOS compatibility when incoming userId contains special characters.
-
-
-## TapSDK V-3.9.0
-Released 2022-05-25
-
-### Features
-- **Payment:** Android provides domestic payment service (in beta).
-
-### Bug fixes
-- **Common:** iOS blocked logging background tasks to avoid crash.
-- **LeanCloud:** Update to 0.10.11 [address](https://github.com/leancloud/csharp-sdk/releases/tag/0.10.11)
-
-### Improvements
-- **Moment:** Dynamic SDK local multi-language resource adaptation.
-
-
-## TapSDK V-3.8.0
-Released 2022-05-09
-
-### Features
-- **Friend:** Add friend mode blacklist.
-
-### Bug fixes
-- **AntiAddiction:** Fixed iOS local cache misconfiguration and optimised the configuration cache logic.
-
-### Improvements
-- **Moment:** Optimise authorisation request logic.
-- **LeanCloud:** Updated to 0.10.10 [address](https://github.com/leancloud/csharp-sdk/releases/tag/0.10.10)
-
-
-## TapSDK V-3.7.1
-Released 2022-04-11
-
-### Features
-- **DB:** Add getting and reporting of boot time.
-
-### Bug fixes
-- **Moment:** Fixed dynamic rotation issue.
-
-### Improvements
-- **AntiAddiction:** Optimise the logic of authorisation from the master.
-
-
-## TapSDK V-3.7.0
-Released 2022-03-28
-
-### Bug fixes
-- **Friend:** Support custom user name and configure additional custom parameters for sharing links.
-- **Friend:** Add interface to parse and process sharing links.
-- **Friend:** Add interface for blacklisting in follow mode.
-- **Friend:** Add rich information data in friend request data.
-- **Friend:** Support finding user information by user ID.
-- **Login:** Add interface to get mutual friend list.
-
-
-## TapSDK V-3.6.3
-Released 2022-03-08
-
-### Features
-- **DB:** Use overseas domain name when initialising region as overseas.
-- **DB:** iOS When the report of playing hours fails at the end of the process, it will try to report the hours again when the process is opened again (if you have already accessed the data analysis function, and the initialisation region is overseas, and you need to migrate the data before upgrading the SDK to 3.6.3, please contact us by submitting a work order).
-- **DB:** iOS internal version is updated to 3.0.9.
-
-### Bug fixes
-- **Bootstrap:** Android LCPush receiver exported parameter changed to true to adapt to Android 12.
-- **Achievement:** Achievement panel does not show rarity text when rarity is 0.
-- **Achievement:** Android resource usage has been changed to R file references.
-- **Login:** Android tries to fix the problem of missing static variables.
-- **Login:** Android resource usage changed to R file references.
-- **Moment:** Android fixes getDialog related NPE on some models.
-- **Moment:** iOS provide api to switch to Controller mode to show UI.
-- **Common:** Android fixes Traditional Chinese recognition on certain models.
-- **Common:** Android fix TapDBConfig initialisation failure when not configured during initialisation.
-- **AntiAddiction:** Improve the user experience when the anti-addiction service is abnormal.
-
-
-## TapSDK V-3.6.1
-Released 2022-02-07
-
-### Bug fixes
-- **RTC:** RTC interface optimisation
-- **Login:** Login UI optimisation
-- **Moment:** Inline dynamic support for multi-language reporting.
-- **AntiAddiction:** Real Name Authentication Interface Removal of Parameter TapToken
-
-
-## TapSDK V-3.6.0
-Released 2022-01-07
-
-### Features
-- **Friend:** Support to get the list of friends of Tap and in-game one-way follow models.
-
-### Bug fixes
-- **License:** DLC fixes the issue that the purchase callback cannot be obtained after directly initiating a purchase request in the query callback.
-
-
-## TapSDK V-3.5.0
-Released 2021-11-30
-
-### Bug fixes
-- **RTC:** Fix range voice failure issue.
-- **Friend:** Find friend by friend code.
-- **Friend:** Add friend by friend code.
-- **Friend:** Query third party friend list
-- **Friend:** Follow TapTap Friends
-- **Achievement:** Fixed the problem of reading local data and recognising achieved achievements as unachieved.
-- **Login:** Inline web login page supports normal display of shaped bangs screen.
-- **DB:** Add soc related parameter to report.
-- **Moment:** Dependency update
-- **Common:** Support update
-- **AntiAddiction:** Added compatibility handling for anti-addiction server exceptions.
-- **AntiAddiction:** Hiding when switching accounts.
-- **AntiAddiction:** Update callback code for switching accounts (from 1000 -> 1001)
-
-
-## TapSDK V-3.4.0
-Released 2021-11-09
-
-### Features
-- **RTC:** Add instant voice call.
-- **DB:** Modify some reporting parameters and fix the issue of duration statistics.
-
-
-## TapSDK V-3.3.1
-Released 2021-10-25
-
-### Features
-- **Friend:** Add multi-language support for friends.
-- **Friend:** Change from prompt to callback when jumping from sharing page to in-app and sending friend request.
-
-### Bug fixes
-- **DB:** Fixed reported address error.
-- **AntiAddiction:** Api add public parameter.
-
-
-## TapSDK V-3.3.0
-Released 2021-10-15
-
-### Features
-- **Support:** New customer service module.
-- **Friend:** Add Friend module.
-- **AntiAddiction:** New anti-addiction module.
-
-### Bug fixes
-- **Achievement:** UI adjustment
-- **Moment:** Optimise statistics parameters
-
-
-## TapSDK V-3.2.0
-Released 2021-09-01
-
-### Breaking changes
-- **Bootstrap:** Migrated `RegionType.cs`, `TapConfig.cs` to `TapCommon` module.
-
-*## Features
-- **Bootstrap:** Supports cloud archiving.
-- **Login:** New PC support.
-- **DB:** Android TapDB supports sandbox reporting.
-- **DB:** iOS support for ASA
-- **DB:** iOS Remove redundant reporting parameter (appId).
-
-### Bug fixes
-- **Moment:** Fix the issue that there is no callback after logging in dynamically.
-- **Moment:** iOS Optimise memory usage after opening dynamics multiple times
-
-
-## TapSDK V-3.1.0
-Released 2021-07-28
-
-### Features
-- **Bootstrap:** Support in-game friends.
-- **Achievement:** Support in-game achievement.
-
-### Bug fixes
-- **Moment:** Fixes a crash when opening Moment on Android 11 devices without certain permissions.
-- **License:** Fix the issue that authentication will crash after successful authentication in some cases.
-
-
-## TapSDK V-3.0.0
-Released 2021-07-16
-
-Tips: Current version does not support v2.x upgrade.
-
-### Breaking changes
-- **Bootstrap:** Account system upgraded to TDSUser
-- **Bootstrap:** Login related interface modification
-- **Bootstrap:** Get Bonfire Qualification interface moved to TapLogin module
-- **Bootstrap:** Removed Set Language interface.
-- **Login:** Open all interfaces, support to get openID and unionID of TapTap account.
-
-### Bug fixes
-- **Moment:** Fix a bug that could cause the page to crash.
-
-
-## TapSDK V-2.1.8
-Released 2021-07-21
-
-### Bug fixes
-- **Moment:** Fix the issue that opening Moment crashes on Android 11 devices without certain permissions.
-- **License:** Fix the issue that authentication will crash after successful authentication in some cases.
-
-
-## TapSDK V-2.1.7
-Released 2021-07-14
-
-### Features
-- **DB:** TapDB recharge interface now supports passing custom fields.
-
-### Improvements
-- **Friend:** TapLogin and TapFriends modified iOS openUrl intercept method.
-
-
-## TapSDK V-2.1.6
-Released 2021-07-01
-
-### Bug fixes
-- **Moment:** Fix the bug that calling [TapMoment close] doesn't work.
-
-
-## TapSDK V-2.1.5
-Released 2021-06-22
-
-### Features
-- **Moment:** New scenario-based callback interface.
-
- ```
- // Scenario callbacks are returned in a unified interface for dynamic callbacks, Code = 70000, content is a string in JSON format.
- {
- sceneId: "taprl0071417002",
- eventType: "READY",
- eventPayload: "{}",
- eventType: "READY", eventPayload: "{}", timestamp: 1622791814130, // ms
- }
- ```
-- **DB:** Android Add Game TapTap Shared ID Switch
-- **Login:** Evoke TapTap client login within CloudPlay.
-
-
-## TapSDK V-2.1.4
-Released 2021-06-10
-
-### Features
-- **Friend:** New interface to set rich message and query rich message.
-- **Friend:** TapUserRelationShip adds online & time & TapRichPresence parameters.
-
-### Bug fixes
-- **Bootstrap:** Internal optimisations
-- **Common:** Optimise multi-language correlation
-- **Common:** Fixed JSON parsing issue.
-
-
-## TapSDK V-2.1.3
-Released 2021-06-03
-
-### Features
-- **Bootstrap:** New multi-language configurations for Traditional Chinese, Japanese, Korean, Thai and Indonesian.
-- **Common:** iOS now supports TapTap and Tap.
-
-### Bug fixes
-- **Friend:** Fixed the possibility of friend popup multiple times in special scenarios.
-- **Moment:** Android UI may be obscured due to judgement failure on some bangs screen devices.
-
-
-## TapSDK V-2.1.2
-Released 2021-05-18
-
-### Breaking changes
-- **Bootstrap:** Deprecates OpenUserCenter interface.
-
-### Features
-- **Friend:** New message callback interface.
- ```c#''
- TapFriends.RegisterMessageListener(ITapMessageListener listener);
- ```
-- **Friend:** Search for friends
- ```c#
- TapFriends.SearchUser(string userId, Action action)
- ```
-- **Friend:** Sharing a friend invitation
- ```c#
- TapFriends.SendFriendInvitation(Action action);;
- ```
-- **Friend:** Get the friend invitation link
- ```c#
- TapFriends.GenerateFriendInvitation(Action action);
- ```
-- **Friend:** Add TapFriends UI
-
-### Bug fixes
-- **Common:** Fix iOS ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES setting issue which may lead to AppStore audit failure.
-
-
-## TapSDK V-2.1.1
-Released 2021-05-11
-
-### Breaking changes
-- **Bootstrap:** LoginType removes Apple, Guest login type.
-- **Bootstrap:** TDS-Info.plist removes Apple_SignIn_Enable configuration.
-- **Bootstrap:** Deprecates the Bind interface.
-- **Bootstrap:** TapConfig Constructor Parameter Changes
-
-### Features
-- **Common:** New TapTap App related support for Android.
-- **Common:** New link support for TapTap.
-- **Common:** Switch between haynesville and overseas domains.
-- **Bootstrap:** New bonfire test eligibility verification
- ``
- TapBootstrap.GetTestQualification((bool, error)=>{ }):.
- ```
-- **Bootstrap:** initial configuration via TapConfig
- * New TapDBConfig for TapDB initialisation configuration.
- * New ClientSecret for TapSDK initialisation.
- ```c#
- // It is recommended to use the following TapConfig constructor for initialisation
- var config = new TapConfig.Builder()
- .ClientID("client_id")
- .ClientSecret("client_secret")
- .RegionType(RegionType.CN)
- .TapDBConfig(true, "gameChannel", "gameVersion", true)
- .ConfigBuilder();
- TapBootstrap.Init(config);
- ```
-- **Friend:** Add Tap friend service.
-- **License:** New DLC purchase service.
-- **Moment:** New DirectlyOpen interface.
- ** Scenario entry
- ```c#
- var sceneDic = new Dictionary(){{TapMomentConstants.TapMomentPageShortCutKey,sceneId}};
-
- TapMoment.DirectlyOpen(orientation,TapMomentConstants.TapMomentPageShortCut,sceneDic);
- ```
-- **DB:** New RegisterDynamicProperties interface.
-- **DB:** New AdvertiserIDCollectionEnabled IDFA get switch.
-
-
-## TapSDK V-2.0.0
-Released 2021-04-08
-
-[TapSDK Developer Centre](https://developer.taptap.cn/docs/en/tap-download/)
\ No newline at end of file
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/best-practice/app-data-share.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/best-practice/app-data-share.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/best-practice/app-data-share.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/best-practice/app-data-share.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/best-practice/custom-reset-verify-page.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/best-practice/custom-reset-verify-page.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/best-practice/custom-reset-verify-page.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/best-practice/custom-reset-verify-page.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/best-practice/network-connectivity-diagnosis.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/best-practice/network-connectivity-diagnosis.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/best-practice/network-connectivity-diagnosis.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/best-practice/network-connectivity-diagnosis.mdx
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/features.mdx
deleted file mode 100644
index 759459259..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/features.mdx
+++ /dev/null
@@ -1,51 +0,0 @@
----
-title: Data Storage Overview
-sidebar_label: Overview
-sidebar_position: 1
----
-
-With TDS’s Data Storage service, your game gains the ability to store JSON objects, binary files, and geolocations on the cloud and retrieve them at any time. The security of the data is guaranteed by the row-level permission control with ACL as well as the user- and role-based permission management system.
-
-## The Problem We’re Solving
-
-Most online applications are data-driven and share a very similar architecture:
-
-- The frontend presents the UI with data and handles interactions with the user while exchanging data with the app server on the backend.
-
-- The app server handles the main logic of the application by either generating data and storing it on the data server or gathering data from the data server and sending it to the frontend.
-
-- The data server stores the data and makes backups of it.
-
-The model mentioned above works for most of the applications we see today. Although these applications share a variety of data structures and logic, they all have data flowing behind the scenes, which drives their functions. They also share a very similar backend framework (like LAMP). However, developers have been building the same system again and again, which tremendously increased the time and cost needed before they could launch their applications.
-
-## Features
-
-You can see TDS’s Data Storage service as an object-oriented database without worrying about the size of the data you’re going to store or the number of requests users will make through your game.
-
-### Structured Data Storage
-
-You can store any kind of JSON object on the cloud and set up associations among objects. Objects can be created, read, updated, and deleted through the APIs we offer.
-
-### File Storage
-
-You can store binary files like images, documents, audios, and videos on the cloud without allocating disk spaces for them in advance. Replicas are automatically created for your files so you don’t have to worry about the security of the files. Plus, your files are served through our CDN network by default so that your users won’t have to experience a delay when accessing them.
-
-### Permission Control With ACL
-
-You can set permissions for classes, rows, and columns to maximize the security of your data.
-
-### Syncing Data Among Devices
-
-Data can be synced among multiple devices, enabling real-time collaborations for your users.
-
-### Data Analytics
-
-With the SQL interface we provide, you can easily process and analyze your data in a parallel manner. This is best for conducting data mining, OLAP, and business intelligence.
-
-## Our Benefits
-
-- The multi-region data replication strategy ensures a 99.999% reliability and enables extremely high concurrent queries for your data.
-
-- With a whole ecosystem of services, you can easily add community features like feeds to your game.
-
-- Our service has been proven to be able to handle over a billion requests every single day. You can be confident that your game will run stably with our service.
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/flutter-guide/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/flutter-guide/_category_.json
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/flutter-guide/_category_.json
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/flutter-guide/_category_.json
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/flutter-guide/flutter.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/flutter-guide/flutter.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/flutter-guide/flutter.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/flutter-guide/flutter.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/flutter-guide/setup-flutter.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/flutter-guide/setup-flutter.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/flutter-guide/setup-flutter.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/flutter-guide/setup-flutter.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/friendship.md b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/friendship.md
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/friendship.md
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/friendship.md
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/go-guide/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/go-guide/_category_.json
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/go-guide/_category_.json
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/go-guide/_category_.json
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/go-guide/go.md b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/go-guide/go.md
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/go-guide/go.md
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/go-guide/go.md
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/go-guide/setup-go.md b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/go-guide/setup-go.md
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/go-guide/setup-go.md
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/go-guide/setup-go.md
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/java-guide/sdk-introduction.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/java-guide/sdk-introduction.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/java-guide/sdk-introduction.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/java-guide/sdk-introduction.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/java-guide/setup-android-securely.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/java-guide/setup-android-securely.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/java-guide/setup-android-securely.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/java-guide/setup-android-securely.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/overview.md b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/overview.md
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/overview.md
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/overview.md
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/php-guide/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/php-guide/_category_.json
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/php-guide/_category_.json
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/php-guide/_category_.json
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/php-guide/php.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/php-guide/php.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/php-guide/php.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/php-guide/php.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/php-guide/setup-php.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/php-guide/setup-php.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/php-guide/setup-php.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/php-guide/setup-php.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/py-guide/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/py-guide/_category_.json
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/py-guide/_category_.json
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/py-guide/_category_.json
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/py-guide/python.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/py-guide/python.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/py-guide/python.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/py-guide/python.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/py-guide/setup-python.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/py-guide/setup-python.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/py-guide/setup-python.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/py-guide/setup-python.mdx
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/rest.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/rest.mdx
deleted file mode 100644
index 8644c2775..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/rest.mdx
+++ /dev/null
@@ -1,2293 +0,0 @@
----
-title: Data Storage REST API
-sidebar_label: REST API
-slug: /sdk/storage/guide/rest/
-sidebar_position: 7
----
-
-You can access the Data Storage service from any device that can send HTTP requests. There are many things you can do with our REST API. For example:
-
-- You can manipulate data on the cloud with any programming language.
-- If you want to migrate from TDS to other services, you can export all your data.
-- Your mobile site can fetch data from the cloud with vanilla JavaScript if you regard importing the JavaScript SDK as an overkill.
-- You can import new data in batches to be consumed by your app later.
-- You can export recent data for offline analysis or additional incremental backup.
-
-## API Version
-
-The current API version is `1.1`.
-
-### Testing
-
-To help you easily test out our REST API, this page provides curl command examples. Those examples are targeted for users of unix-like platforms (including macOS and Linux), so you may need to modify the commands if you are using cmd.exe on Windows.
-For example, `\` in curl examples means to be continued on the next line, but cmd.exe will consider it as a path separator.
-To make things easier, we recommend you to use [Postman](https://www.postman.com/) for testing on Windows.
-You can directly import the curl commands shown on this page into Postman.
-
-![Click on “Import” and paste the curl command under the “Raw Text” tab](/img/postman-import-curl.png)
-
-With Postman, you can also generate code for the languages and libraries of your choice for accessing our REST API.
-
-![Click on “Code” and select the language and library you use](/img/postman-generate-code.png)
-
-### Base URL
-
-The Base URL for the REST API (represented with `{{host}}` in curl examples) is the API domain of your app. You can manage and view it on the dashboard.
-
-### Objects
-
-| URL | HTTP Method | Functionality |
-| ----------------------------------------------- | ----------- | -------------------- |
-| /1.1/classes/<className> | POST | Create an object |
-| /1.1/classes/<className>/<objectId> | GET | Retrieve an object |
-| /1.1/classes/<className>/<objectId> | PUT | Update an object |
-| /1.1/classes/<className> | GET | Query objects |
-| /1.1/classes/<className>/<objectId> | DELETE | Delete an object |
-| /1.1/scan/classes/<className> | GET | Iterate over objects |
-
-### Users
-
-| URL | HTTP Method | Functionality |
-| ----------------------------------------------- | ----------- | -------------------------------------------------- |
-| /1.1/users | POST | Register Connect user |
-| /1.1/usersByMobilePhone | POST | Register or log in via mobile phone |
-| /1.1/login | POST | Log in |
-| /1.1/users/<objectId> | GET | Retrieve a user |
-| /1.1/users/me | GET | Retrieve a user with session token |
-| /1.1/users/<objectId>/refreshSessionToken | PUT | Reset session token |
-| /1.1/users/<objectId>/updatePassword | PUT | Reset password with old password |
-| /1.1/users/<objectId> | PUT | Update user info Connect user Verify email |
-| /1.1/users | GET | Query users |
-| /1.1/users/<objectId> | DELETE | Delete a user |
-| /1.1/requestPasswordReset | POST | Request to reset password with email |
-| /1.1/requestEmailVerify | POST | Request to verify email |
-
-### Roles
-
-| URL | HTTP Method | Functionality |
-| --------------------------- | ----------- | --------------- |
-| /1.1/roles | POST | Create a role |
-| /1.1/roles/<objectId> | GET | Retrieve a role |
-| /1.1/roles/<objectId> | PUT | Update a role |
-| /1.1/roles | GET | Query roles |
-| /1.1/roles/<objectId> | DELETE | Delete a role |
-
-### Data Schema
-
-| URL | HTTP Method | Functionality |
-| ------------------------------ | ----------- | ------------------------------- |
-| /1.1/schemas | GET | Retrieve schemas of all classes |
-| /1.1/schemas/<className> | GET | Retrieve the schema of a class |
-
-### Others
-
-| URL | HTTP Method | Functionality |
-| -------------------------- | ----------- | --------------------------------------------------- |
-| /1.1/date | GET | Retrieve server date and time |
-| /1.1/exportData | POST | Request to export all data from the app |
-| /1.1/exportData/<id> | GET | Retrieve the status and result of a data export job |
-
-### Request Format
-
-For POST and PUT requests, the request body must be in JSON, and the `Content-Type` HTTP header should be `application/json` accordingly.
-
-The `X-LC-Id` and `X-LC-Key` headers are used for authentication:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{"content": "The content of this blog post"}' \
- https://{{host}}/1.1/classes/Post/
-```
-
-`X-LC-Id` is the `App ID` and `X-LC-Key` is the `App Key` or `Master Key`.
-A `,master` postfix is used to indicate that the value of `X-LC-Key` is a `Master Key`. For example:
-
-```
-X-LC-Key: {{masterkey}},master
-```
-
-Cross-origin resource sharing is supported so that you can use these headers with `XMLHttpRequest` in JavaScript.
-
-You can also use the `Accept-Encoding` header to enable compression with `gzip` or `brotli`.
-
-#### X-LC-Sign
-
-You may also authenticate requests with `X-LC-Sign` instead of `X-LC-Key` to minimize the risk of leaking the `App Key`:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Sign: d5bcbb897e19b2f6633c716dfdfaf9be,1453014943466" \
- -H "Content-Type: application/json" \
- -d '{"content": "Updating a post with the X-LC-Sign header"}' \
- https://{{host}}/1.1/classes/Post/
-```
-
-The value of `X-LC-Sign` is a string in the form of `sign,timestamp[,master]`:
-
-| Name | Optionality | Description |
-| --------- | ----------- | ------------------------------------------------------------------------------------ |
-| sign | Required | Concat timestamp and `App Key` (or `Master Key`), then calculate its MD5 hash value. |
-| timestamp | Required | The unix timestamp of the current request, accurate to **milliseconds**. |
-| master | Optional | Use this postfix to indicate that the Master Key is used. |
-
-Please make sure the letters in the MD5 hash value in the `sign` portion are in lowercase.
-
-For example, given the following application:
-
-
-
-
-
App Id
-
- FFnN2hso42Wego3pWq4X5qlu
-
-
-
-
App Key
-
- UtOCzqb67d3sN12Kts4URwy8
-
-
-
-
Master Key
-
- DyJegPlemooo4X1tg94gQkw1
-
-
-
-
Request time
-
2016-01-17 15:15:43.466 GMT+08:00
-
-
-
timestamp
-
- 1453014943466
-
-
-
-
-
-**To calculate the sign with `App Key`**:
-
-```
-md5( timestamp + App Key )
-= md5(1453014943466UtOCzqb67d3sN12Kts4URwy8)
-= d5bcbb897e19b2f6633c716dfdfaf9be
-```
-
-```sh
- -H "X-LC-Sign: d5bcbb897e19b2f6633c716dfdfaf9be,1453014943466" \
-```
-
-**To calculate the sign with `Master Key`**:
-
-```
-md5( timestamp + Master Key )
-= md5(1453014943466DyJegPlemooo4X1tg94gQkw1)
-= e074720658078c898aa0d4b1b82bdf4b
-```
-
-```sh
- -H "X-LC-Sign: e074720658078c898aa0d4b1b82bdf4b,1453014943466,master" \
-```
-
-Here `,master` is added to the end of the string to tell the server that the signature is generated with the Master Key.
-
-**Using master key will bypass all permission validations. Make sure you do not leak the master key and only use it in restrained environments.**
-
-#### Specifying Hook Invocation Environment
-
-For requests that may trigger hooks on Cloud Engine, use the `X-LC-Prod` HTTP header to specify the invocation environment:
-
-- `X-LC-Prod: 0` means to use the staging environment
-- `X-LC-Prod: 1` means to use the production environment
-
-If you do not specify the `X-LC-Prod` HTTP header, the hook in the production environment will be invoked.
-
-### Response Format
-
-For all the requests made to the REST API, The response body is always a JSON object.
-
-An HTTP status code is used to indicate whether a request succeeded or failed.
-A 2xx status code indicates success, and a 4xx/5xx status code indicates the occurrence of an error.
-When an error occurs, the response body will be a JSON object with two fields, `code` and `error`,
-where `code` is the error code (integer) and `error` is a brief error message (string).
-The `code` may be identical to the HTTP status code,
-but oftentimes it is a customized error code more specific than the HTTP status code.
-For example, if you try to save an object with an invalid key name, you will see:
-
-```json
-{
- "code": 105,
- "error": "Invalid key name. Keys are case-sensitive and 'a-zA-Z0-9_' are the only valid characters. The column is: 'invalid?'."
-}
-```
-
-## Objects
-
-### Object Format
-
-The Data Storage service is built around objects.
-Each object consists of several key-value pairs,
-where values are in JSON-compatible formats.
-Objects are schemaless, so you do not need to allocate keys in advance.
-You only need to set key-value pairs as you wish and when needed.
-
-For example, if you are implementing a Twitter-like social app, you may give the following attributes (key-value pairs) to a post:
-
-```json
-{
- "content": "Discover Superb Games.",
- "pubUser": "TapTap",
- "pubTimestamp": 1435541999
-}
-```
-
-Keys can only contain letters, numbers, and underscores.
-Values can be anything encoded in JSON.
-
-Each object belongs to a class (table in traditional database terms).
-We recommend using `CapitalizedWords` to name your classes, and `mixedCases` to name your attributes.
-This naming style helps to improve the readability of your code.
-
-Each time when an object is saved to the cloud, a unique `objectId` will be assigned to it.
-`createdAt` and `updatedAt` will also be filled in by the cloud, which indicate the time the object is created and updated.
-These attributes are reserved and you cannot modify them yourself.
-For example, the object above could look like this when retrieved:
-
-```json
-{
- "content": "Discover Superb Games.",
- "pubUser": "TapTap",
- "pubTimestamp": 1435541999,
- "createdAt": "2015-06-29T01:39:35.931Z",
- "updatedAt": "2015-06-29T01:39:35.931Z",
- "objectId": "558e20cbe4b060308e3eb36c"
-}
-```
-
-`createdAt` and `updatedAt` are strings whose content is a UTC timestamp in ISO 8601 format with millisecond precision: `YYYY-MM-DDTHH:MM:SS.MMMZ`.
-`objectId` is a string unique in the class, like the primary key of a relational database.
-
-In our REST API, class-level operations use the class name as its endpoint.
-For example, the URL for operations on a class named `Post` will be:
-
-```
-https://{{host}}/1.1/classes/Post
-```
-
-There is also a special URL for **users**:
-
-```
-https://{{host}}/1.1/users
-```
-
-Object-specific operations use nested URLs under the class.
-For example, the URL for operations on an object in the `Post` class with `558e20cbe4b060308e3eb36c` as its `objectId` will be:
-
-```
-https://{{host}}/1.1/classes/Post/558e20cbe4b060308e3eb36c
-```
-
-### Creating Objects
-
-To create a new object, send a **POST** request containing the object itself to the URL for the class.
-For example, to create the object we mentioned earlier:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{"content": "Discover Superb Games.","pubUser": "TapTap","pubTimestamp": 1435541999}' \
- https://{{host}}/1.1/classes/Post
-```
-
-If succeeded, you will receive `201 Created` with a `Location` header point to the URL of the object just created:
-
-```sh
-Status: 201 Created
-Location: https://{{host}}/1.1/classes/Post/558e20cbe4b060308e3eb36c
-```
-
-And the response body is a JSON object with `objectId` and `createdAt` key-value pairs:
-
-```json
-{
- "createdAt": "2015-06-29T01:39:35.931Z",
- "objectId": "558e20cbe4b060308e3eb36c"
-}
-```
-
-To tell the cloud to return the full data of the new object, set the `fetchWhenSave` parameter to `true`:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{"content": "Discover Superb Games.","pubUser": "TapTap","pubTimestamp": 1435541999}' \
- https://{{host}}/1.1/classes/Post?fetchWhenSave=true
-```
-
-Class names can only contain letters, numbers, and underscores.
-**Every application can contain up to 500 classes, and each class can contain up to 300 fields.** There is no limit on the number of objects in each class.
-
-### Retrieving Objects
-
-After you create an object, you can send a GET request to the `Location` of the response to fetch the object. For example, to fetch the object we just created:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- https://{{host}}/1.1/classes/Post/558e20cbe4b060308e3eb36c
-```
-
-The response body is a JSON object containing all the attributes you gave to the object, as well as the three preserved attributes (`objectId`, `createdAt`, and `updatedAt`):
-
-```json
-{
- "content": "Discover Superb Games.",
- "pubUser": "TapTap",
- "pubTimestamp": 1435541999,
- "createdAt": "2015-06-29T01:39:35.931Z",
- "updatedAt": "2015-06-29T01:39:35.931Z",
- "objectId": "558e20cbe4b060308e3eb36c"
-}
-```
-
-If the object contains pointers to other objects, you can add an `include` parameter to indicate that you wish to retrieve these objects as well. For example, if a post has an `author` field indicating the person who posted it, you can retrieve the post together with its author in this way:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'include=author' \
- https://{{host}}/1.1/classes/Post/
-```
-
-You can use a dot (`.`) in the value of the `include` parameter to further include the object pointed by a pointed object. For example, assuming that there is a `department` field for each author, you can use `include=author.department` to retrieve the information of the department.
-
-If the class does not exist, you will receive a `404 Not Found` error:
-
-```json
-{
- "code": 101,
- "error": "Class or object doesn't exists."
-}
-```
-
-If the server cannot find the object according to the `objectId` you specified, you will receive an empty object (with HTTP status code being `200 OK`):
-
-```json
-{}
-```
-
-One exception is that for the built-in classes (those with a name starting with a leading underscore), you may get a different result when trying to retrieve an object with an invalid `objectId`.
-For example, when retrieving a `_User` object with an `objectId` that does not exist, you will get a `400 Bad Request` error.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- https://{{host}}/1.1/classes/_User/
-```
-
-The request above will lead to the following response:
-
-```json
-{
- "code": 211,
- "error": "Could not find user."
-}
-```
-
-We recommend using `GET /users/` to fetch user information instead of directly querying the `_User` class.
-See also [Retrieving Users](#retrieving-users).
-
-### Updating Objects
-
-To update an object, you can send a PUT request to the object URL.
-The server will only update the attributes you explicitly specified in the request (except for `updatedAt`).
-For example, to only update the `content` of a post:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{"content": "Discover Superb Games. https://www.taptap.io/"}' \
- https://{{host}}/1.1/classes/Post/
-```
-
-If the update succeeds, a JSON object containing an `updatedAt` property will be returned:
-
-```json
-{
- "updatedAt": "2015-06-30T18:02:52.248Z"
-}
-```
-
-The `fetchWhenSave` parameter can also be used when updating an object.
-Keep in mind that you will only get the updated fields instead of all the fields of the object.
-
-#### Counter
-
-For an existing post in our app, we may want to keep track of how many people liked it. However, since a lot of likes could happen at the same time, if we have the client retrieve the value of the number of likes, update it, and store the new value back to the cloud, there will likely be conflicts that cause the number stored on the cloud to be inaccurate.
-To solve the problem, you can use the `Increment` atomic operator to increase a counter-like attribute:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{"upvotes":{"__op":"Increment","amount":1}}' \
- https://{{host}}/1.1/classes/Post/
-```
-
-The command above adds 1 to the `upvotes` property of the object. You can specify how much you want to increment with the `amount` parameter. The number can be negative to indicate a subtraction from the original value.
-
-There is also a `Decrement` operator.
-`Decrement`ing a positive number is equivalent to `Increment`ing a negative number.
-
-#### Bitwise Operators
-
-There are three bitwise operators for integers:
-
-- `BitAnd`: Bitwise AND
-- `BitOr`: Bitwise OR
-- `BitXor`: Bitwise XOR
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{"flags":{"__op":"BitOr","value": 0x0000000000000004}}' \
- https://{{host}}/1.1/classes/Post/
-```
-
-#### Arrays
-
-There are three atomic operators for arrays:
-
-- `Add` extends an array attribute by appending elements to the given array.
-- `AddUnique` is similar to `Add`, but only appends elements not already contained in the array attribute.
-- `Remove` removes all occurrences of elements specified in the given array.
-
-The given array mentioned above is passed in as the value of the `objects` key.
-
-For example, to add some tags to the post:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{"tags":{"__op":"AddUnique","objects":["Frontend","JavaScript"]}}' \
- https://{{host}}/1.1/classes/Post/
-```
-
-#### Conditional Updates
-
-Suppose we are going to deduct some money from an `Account`, and we want to make sure this deduction will not result in a negative balance.
-We can use conditional updates by adding a `where` parameter with the condition `balance >= amount`:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{"balance":{"__op":"Decrement","amount": 30}}' \
- "https://{{host}}/1.1/classes/Account/558e20cbe4b060308e3eb36c?where=%7B%22balance%22%3A%7B%22%24gte%22%3A%2030%7D%7D"
-```
-
-Here `%7B%22balance%22%3A%7B%22%24gte%22%3A%2030%7D%7D` is the URL-encoded condition `{"balance":{"$gte": 30}}`.
-
-If the condition is not met, the update will not be performed, and you will receive a `305` error:
-
-```json
-{
- "code": 305,
- "error": "No effect on updating/deleting a document."
-}
-```
-
-**Note: `where` must be passed in as a query parameter of the URL.**
-
-#### A List of \_\_op Operations
-
-You can perform atomic operations with the `__op("Method", {JSON parameters})` function.
-
-| Operation | Description | Example |
-| -------------- | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
-| Delete | Delete a property of the object | `__op('Delete', {'delete': true})` |
-| Add | Add objects to the end of an array | `__op('Add',{'objects':['Apple','Google']})` |
-| AddUnique | Add each of the objects to the end of an array only if the object does not exist in the array | `__op('AddUnique', {'objects':['Apple','Google']})` |
-| Remove | Delete objects from the array | `__op('Remove',{'objects':['Apple','Google']})` |
-| AddRelation | Add a relation | `__op('AddRelation', {'objects':[pointer('_User','558e20cbe4b060308e3eb36c')]})` |
-| RemoveRelation | Remove a relation | `__op('RemoveRelation', {'objects':[pointer('_User','558e20cbe4b060308e3eb36c')]})` |
-| Increment | Increment | `__op('Increment', {'amount': 50})` |
-| Decrement | Decrement | `__op('Decrement', {'amount': 50})` |
-| BitAnd | Bitwise AND | `__op('BitAnd', {'value': 0x0000000000000004})` |
-| BitOr | Bitwise OR | `__op('BitOr', {'value': 0x0000000000000004})` |
-| BitXor | Bitwise XOR | `__op('BitXor', {'value': 0x0000000000000004})` |
-
-### Deleting Objects
-
-To delete an object, send a `DELETE` request to the URL for the object:
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- https://{{host}}/1.1/classes/Post/
-```
-
-To delete an attribute from an object, send a `PUT` request with the `Delete` operator:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{"downvotes":{"__op":"Delete"}}' \
- https://{{host}}/1.1/classes/Post/
-```
-
-#### Conditional Deletions
-
-Similar to conditional updates, we pass a URL-encoded `where` parameter to the `DELETE` request to conditionally delete the object. For example, to delete a post if its `clicks` equals to `0`:
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- "https://{{host}}/1.1/classes/Post/?where=%7B%22clicks%22%3A%200%7D"
-```
-
-Here `%7B%22clicks%22%3A%200%7D` is the URL-encoded value for `{"clicks": 0}`.
-
-Again, if the condition is not met, the update will not be performed, and you will receive a `305` error:
-
-```json
-{
- "code": 305,
- "error": "No effect on updating/deleting a document."
-}
-```
-
-**Keep in mind that `where` must be a query parameter in the URL.**
-
-### Iterating Over Objects
-
-For classes with a moderate number of objects, we can iterate over all the objects in the class by constructing queries with `skip`, `limit`, and `order`.
-However, for classes with a large number of objects, there will be a performance issue if we keep using `skip`.
-To avoid this problem, we can do pagination by specifying the scope of `createdAt` or `updatedAt`.
-There is a `scan` endpoint that is dedicated for this purpose: it can be used to iterate over the objects in a class ordered by a given field. Using `scan` makes things easier compared to constructing queries manually by specifying the scopes of `createdAt` or `updatedAt`.
-By default, `scan` returns 100 results in ascending order by `objectId`. You can ask the cloud to return up to 1000 results by specifying the `limit` parameter:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -G \
- --data-urlencode 'limit=10' \
- https://{{host}}/1.1/scan/classes/Article
-```
-
-Be sure to use the `MasterKey` when using `scan`.
-
-The cloud will return a `results` array and a `cursor`.
-
-```json
-{
- "results": [
- {
- "tags": ["clojure", "\u7b97\u6cd5"],
- "createdAt": "2016-07-07T08:54:13.250Z",
- "updatedAt": "2016-07-07T08:54:50.268Z",
- "title": "clojure persistent vector",
- "objectId": "577e18b50a2b580057469a5e"
- }
- //...
- ],
- "cursor": "pQRhIrac3AEpLzCA"
-}
-```
-
-The `cursor` will be `null` if there are no more results.
-If `cursor` is not `null`, you can use `scan` again with the value of `cursor` to continue the iteration:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -G \
- --data-urlencode 'limit=10' \
- --data-urlencode 'cursor=pQRhIrac3AEpLzCA' \
- https://{{host}}/1.1/scan/classes/Article
-```
-
-Each `cursor` must be consumed within 10 minutes.
-It becomes invalid after 10 minutes.
-
-You can also specify `where` conditions for filtering:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -G \
- --data-urlencode 'limit=10' \
- --data-urlencode 'where={"score": 100}' \
- https://{{host}}/1.1/scan/classes/Article
-```
-
-As mentioned above, by default the results are in ascending order by `objectId`.
-To return results ordered by another attribute,
-pass that attribute as the `scan_key` parameter:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -G \
- --data-urlencode 'limit=10' \
- --data-urlencode 'scan_key=score' \
- https://{{host}}/1.1/scan/classes/Article
-```
-
-To return results in descending order, prefix a minus sign (`-`) to the value of the `scan_key`, e.g., `-score`.
-
-**The value of the `scan_key` passed must be strictly monotonous, and it cannot be used in `where` conditions.**
-
-**You cannot set the `include` parameter when using `scan`.**
-If you wish to use `include` when iterating over the objects in a class, please use a basic query with setting the scopes of `createdAt` and `updatedAt` instead.
-
-### Batch Operations
-
-To reduce the number of requests you make, you can wrap create, update, and delete operations on multiple objects in one request.
-
-You can assign each operation its own method, path, and body, which replaces the HTTP requests you would ordinarily make. The operations will be executed according to the order they are sent to the server. For example, to make a series of posts at once:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "requests": [
- {
- "method": "POST",
- "path": "/1.1/classes/Post",
- "body": {
- "content": "Post 1",
- "pubUser": "TapTap"
- }
- },
- {
- "method": "POST",
- "path": "/1.1/classes/Post",
- "body": {
- "content": "Post 2",
- "pubUser": "TapTap"
- }
- }
- ]
- }' \
- https://{{host}}/1.1/batch
-```
-
-Currently, there is no limit on the number of operations in each request, but there is a 20 MB size limit on the request body for all API requests.
-It is recommended that you load at most 100 operations into each request.
-
-The response body will be an array with the length and order of the members corresponding to those of the operations in the request.
-Each member will be a JSON object with one and only one key, and that key will be either `success` or `error`.
-The value of `success` or `error` will be the response to the corresponding single request on success or failure respectively.
-
-```json
-[
- {
- "error": {
- "code": 1,
- "error": "Could not find object by id '558e20cbe4b060308e3eb36c' for class 'Post'."
- }
- },
- {
- "success": {
- "updatedAt": "2017-02-22T06:35:29.419Z",
- "objectId": "58ad2e850ce463006b217888"
- }
- }
-]
-```
-
-Be aware that the HTTP status `200` returned by a batch request only means the cloud had received and performed the operations.
-It does not mean that all the operations within the batch request succeeded.
-
-Besides the `POST` requests in the above example,
-you can also wrap `PUT` and `DELETE` requests in a batch request:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "requests": [
- {
- "method": "PUT",
- "path": "/1.1/classes/Post/55a39634e4b0ed48f0c1845b",
- "body": {
- "upvotes": 2
- }
- },
- {
- "method": "DELETE",
- "path": "/1.1/classes/Post/55a39634e4b0ed48f0c1845c"
- }
- ]
- }' \
- https://{{host}}/1.1/batch
-```
-
-Batch requests can also be used to replace requests with very long URLs (usually constructed with very complex queries or conditions) to bypass the limit on URL length enforced by the server side or the client side.
-
-### Advanced Data Types
-
-Besides standard JSON values, we also support advanced data types like Date, Byte, and Pointer.
-These advanced data types are encoded as a JSON object with a `__type` key.
-
-**Date** contains an `iso` key, whose value is a UTC timestamp string in ISO 8601 format with millisecond precision: `YYYY-MM-DDTHH:MM:SS.MMMZ`.
-
-```json
-{
- "__type": "Date",
- "iso": "2015-06-21T18:02:52.249Z"
-}
-```
-
-The **Date** type can be useful when you perform queries on the built-in `createdAt` and `updatedAt` fields. For example, to query all the posts made on a specific time:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -G \
- --data-urlencode 'where={"createdAt":{"$gte":{"__type":"Date","iso":"2015-06-21T18:02:52.249Z"}}}' \
- https://{{host}}/1.1/classes/Post
-```
-
-Keep in mind that since `createdAt` and `updatedAt` are built-in fields, when their values are appearing in the response of a request, they will be in UTC timestamps rather than encoded Dates.
-
-**Byte** contains a `base64` key, whose value is a MIME base64 string (with no whitespace characters).
-
-```json
-{
- "__type": "Bytes",
- "base64": "5b6I5aSa55So5oi36KGo56S65b6I5Zac5qyi5oiR5Lus55qE5paH5qGj6aOO5qC877yM5oiR5Lus5bey5bCGIExlYW5DbG91ZCDmiYDmnInmlofmoaPnmoQgTWFya2Rvd24g5qC85byP55qE5rqQ56CB5byA5pS+5Ye65p2l44CC"
-}
-```
-
-**Pointer** contains a `className` key and an `objectId` key, whose values are the corresponding class name and objectId of the pointed value.
-
-```json
-{
- "__type": "Pointer",
- "className": "Post",
- "objectId": "55a39634e4b0ed48f0c1845c"
-}
-```
-
-A pointer to a user contains a `className` of `_User`.
-The leading underscore indicates that `_User` is a built-in class.
-Similarly, pointers to roles and installations contain a `className` of `_Role` or `_Installation` respectively.
-However, a pointer to a file is a special case:
-
-```json
-{
- "id": "543cbaede4b07db196f50f3c",
- "__type": "File"
-}
-```
-
-**GeoPoint** contains `latitude` and `longitude` of the location:
-
-```json
-{
- "__type": "GeoPoint",
- "latitude": 39.9,
- "longitude": 116.4
-}
-```
-
-We may add more advanced data types in the future, so you should not use `__type` as the key of your own JSON objects.
-
-## Queries
-
-### Basic Queries
-
-To list objects in a class, just send a GET request to the class URL. For example, to get all the posts:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- https://{{host}}/1.1/classes/Post
-```
-
-The response body is a JSON object containing a `results` key,
-whose value is an array of objects:
-
-```json
-{
- "results": [
- {
- "content": "Post 1",
- "pubUser": "TapTap",
- "upvotes": 2,
- "createdAt": "2015-06-29T03:43:35.931Z",
- "objectId": "55a39634e4b0ed48f0c1845b"
- },
- {
- "content": "Post 2",
- "pubUser": "TapTap",
- "pubTimestamp": 1435541999,
- "createdAt": "2015-06-29T01:39:35.931Z",
- "updatedAt": "2015-06-29T01:39:35.931Z",
- "objectId": "558e20cbe4b060308e3eb36c"
- }
- ]
-}
-```
-
-The values of `createdAt` and `updatedAt` you see on the dashboard are in the timezone of your local computer, and it is the same for the values obtained through our SDKs. However, when using the REST API, you will get those values in UTC. You can convert them to local times yourself if you need them.
-
-### Query Constraints
-
-The `where` parameter can be used to apply query constraints.
-It should be encoded as JSON first, then URL encoded.
-
-The simplest form of `where` parameter is a key-value pair (exact match).
-For example, to query posts published by `TapTap`:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -G \
- --data-urlencode 'where={"pubUser":"TapTap"}' \
- https://{{host}}/1.1/classes/Post
-```
-
-Other operators available for the `where` parameter:
-
-| Operator | Description |
-| ------------- | ------------------------------------- |
-| `$ne` | Not equal to |
-| `$lt` | Less than |
-| `$lte` | Less than or equal to |
-| `$gt` | Greater than |
-| `$gte` | Greater than or equal to |
-| `$regex` | Match a regular expression |
-| `$in` | Contain |
-| `$nin` | Not contain |
-| `$all` | Contain all (for array type) |
-| `$exists` | The given key exists |
-| `$select` | Match the result of another query |
-| `$dontSelect` | Not match the result of another query |
-
-For example, to query all the posts published on **2015-06-29**:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -G \
- --data-urlencode 'where={"createdAt":{"$gte":{"__type":"Date","iso":"2015-06-29T00:00:00.000Z"},"$lt":{"__type":"Date","iso":"2015-06-30T00:00:00.000Z"}}}' \
- https://{{host}}/1.1/classes/Post
-```
-
-To query all the posts whose number of votes is an odd number less than 10:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -G \
- --data-urlencode 'where={"upvotes":{"$in":[1,3,5,7,9]}}' \
- https://{{host}}/1.1/classes/Post
-```
-
-To query all the posts _not_ published by TapTap:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -G \
- --data-urlencode 'where={"pubUser":{"$nin":["TapTap"]}}' \
- https://{{host}}/1.1/classes/Post
-```
-
-To query all the posts that have been voted by someone:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -G \
- --data-urlencode 'where={"upvotes":{"$exists":true}}' \
- https://{{host}}/1.1/classes/Post
-```
-
-To query all the posts that have not been voted:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -G \
- --data-urlencode 'where={"upvotes":{"$exists":false}}' \
- https://{{host}}/1.1/classes/Post
-```
-
-Suppose we use `_Followee` and `_Follower` classes for the following relationship, then we can query posts published by someone followed by the current user like this:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -G \
- --data-urlencode 'where={
- "author": {
- "$select": {
- "query": {
- "className":"_Followee",
- "where": {
- "user":{
- "__type": "Pointer",
- "className": "_User",
- "objectId": "55a39634e4b0ed48f0c1845c"
- }
- }
- },
- "key":"followee"
- }
- }
- }' \
- https://{{host}}/1.1/classes/Post
-```
-
-The `order` parameter can be used to specify how the returned objects shuold be sorted. For example, to query posts and sort them in ascending order by creation time:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'order=createdAt' \
- https://{{host}}/1.1/classes/Post
-```
-
-To sort them in descending order:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'order=-createdAt' \
- https://{{host}}/1.1/classes/Post
-```
-
-To sort results by multiple keys, use a comma to separate the keys. For example, to sort posts in ascending order by `createdAt` and descending order by `pubUser`:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'order=createdAt,-pubUser' \
- https://{{host}}/1.1/classes/Post
-```
-
-You can implement paginiation with `limit` and `skip`. `limit` defaults to 100 but you can set it to any integer between 1 and 1000. If you give it a value out of this range, the default value (100) will be used. For example, to get the first 200 posts after the 400th:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'limit=200' \
- --data-urlencode 'skip=400' \
- https://{{host}}/1.1/classes/Post
-```
-
-You can limit the fields being returned from the server by providing a `keys` parameter in your request. For example, to only include `pubUser` and `content` in the response (together with the built-in fields `objectId`, `createdAt`, and `updatedAt`):
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'keys=pubUser,content' \
- https://{{host}}/1.1/classes/Post
-```
-
-You can further limit the returned values to the properties of a given field. For example, to only get the family names of the authors, you can write `keys=pubUser.familyName`.
-
-You can also specify which fields you want to exclude from the response by adding a minus sign before the field names. For example, to exclude the `author` field:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'keys=-author' \
- https://{{host}}/1.1/classes/Post
-```
-
-The inverted selection applies to preserved attributes as well. For example, you can write `keys=-createdAt,-updatedAt,-objectId`.
-You can also use it with dot notation, e.g., `keys=-pubUser.createdAt,-pubUser.updatedAt`.
-
-### Including ACL in the Response
-
-By default, the response will not contain the ACL field.
-The ACL field will only be included if you enabled **Include ACL with objects being queried** under **Developer Center > Your Game > Game Services > Cloud Services > Data Storage > Settings > Queries** and you included `returnACL=true` in the request.
-
-All the parameters mentioned above can be combined.
-
-### Regex Queries
-
-To query posts whose title begins with `WTO`:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -G \
- --data-urlencode 'where={"title":{"$regex":"^WTO.*","$options":"i"}}' \
- https://{{host}}/1.1/classes/Post
-```
-
-We will use the following dataset to demonstrate how you can use `$options` to match different values of **title**:
-
-```
-{ "_id" : 100, "title" : "Single line description." },
-{ "_id" : 101, "title" : "First line\nSecond line" },
-{ "_id" : 102, "title" : "Many spaces before line" },
-{ "_id" : 103, "title" : "Multiple\nline description" },
-{ "_id" : 104, "title" : "abc123" }
-```
-
-| Option | Description | Example |
-| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `i` | **Case-insensitive search** | `{"$regex":"single", "$options":"i"}` will match
`{ "_id" : 100, "title" : "Single line description." }`
|
-| `m` | **Multi-line search** Can be used for strings containing `\n` | `{"$regex":"^S", "$options":"m"}` (starts with capital “S”) will match
|
-| `x` | **Free-spacing and line comments** Includes spaces, tabs, `\n`, and comments starting with `#`, but does not include vertical tabs (ASCII: 11). | `{"$regex":"abc #category code\n123 #item number", "$options":"x"}` (comments after #) will match
`{ "_id" : 104, "title" : "abc123" }`
|
-| `s` | **Allow `.` to match newline characters** | `{"$regex":"m.*line", "$options":"si"}` will match
|
-
-The options above can be combined, for example `"$options":"sixm"`.
-
-### Array Queries
-
-With `key` being an array field, to query all the objects with `key` containing `2`:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'where={"arrayKey":2}' \
- https://{{host}}/1.1/classes/TestObject
-```
-
-To query all the objects with `key` containing `2`, `3`, or `4`:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'where={"arrayKey":{"$in":[2,3,4]}}' \
- https://{{host}}/1.1/classes/TestObject
-```
-
-Using `$all` to query all the objects with `key` containing `2`, `3`, **and** `4`:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'where={"arrayKey":{"$all":[2,3,4]}}' \
- https://{{host}}/1.1/classes/TestObject
-```
-
-Using `$size` to query all the objects with `key` containing exactly 3 objects:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'where={"arrayKey":{"$size": 3}}' \
- https://{{host}}/1.1/classes/TestObject
-```
-
-### Pointer Queries
-
-There are a couple of ways you can use to query relations between objects. If you want to query objects that have a field pointing to a specific object, you can construct a Pointer and pass it to the `where` parameter. Assuming there is a `Comment` class with a `post` field pointing to the `Post` class, you can query all the comments under a post with this command:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'where={"post":{"__type":"Pointer","className":"Post","objectId":"558e20cbe4b060308e3eb36c"}}' \
- https://{{host}}/1.1/classes/Comment
-```
-
-To query objects with pointers according to another query on the pointed object, you can use the `$inQuery` operator. Keep in mind that the default value of `limit` is 100 and the maximum value of it is 1000, and this restriction applies to inner queries as well. You may need to carefully construct queries to get your expected result.
-
-For example, assuming each post has an `image` field, to query all the comments on posts with attached images:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'where={"post":{"$inQuery":{"where":{"image":{"$exists":true}},"className":"Post"}}}' \
- https://{{host}}/1.1/classes/Comment
-```
-
-To include pointed objects in one query, use the `include` parameter.
-For example, to query the most recent 10 comments with the posts commented on:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'order=-createdAt' \
- --data-urlencode 'limit=10' \
- --data-urlencode 'include=post' \
- https://{{host}}/1.1/classes/Comment
-```
-
-Without the `include` parameter, the post attribute of the returned comments will look like this:
-
-```json
-{
- "__type": "Pointer",
- "className": "Post",
- "objectId": "51e3a359e4b015ead4d95ddc"
-}
-```
-
-With the `include=post` parameter, the post attribute will be dereferenced:
-
-```json
-{
- "__type": "Object",
- "className": "Post",
- "objectId": "51e3a359e4b015ead4d95ddc",
- "createdAt": "2015-06-29T09:31:20.371Z",
- "updatedAt": "2015-06-29T09:31:20.371Z",
- "desc": "this is a post"
-}
-```
-
-You can use dots (`.`) for multi-level dereference. For example, to get the `author`s of the posts pointed by comments:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'order=-createdAt' \
- --data-urlencode 'limit=10' \
- --data-urlencode 'include=post.author' \
- https://{{host}}/1.1/classes/Comment
-```
-
-And you can use comma (`,`) to separate multiple pointers to `include`.
-
-### GeoPoint Queries
-
-Early on we have briefly described GeoPoint.
-
-Assuming we are including the location information of each post in the `location` field, you can use the `$nearSphere` operator to query nearby objects. For example, to retrieve 10 posts whose locations are the closest to the current location:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'limit=10' \
- --data-urlencode 'where={
- "location": {
- "$nearSphere": {
- "__type": "GeoPoint",
- "latitude": 39.9,
- "longitude": 116.4
- }
- }
- }' \
- https://{{host}}/1.1/classes/Post
-```
-
-The returned results will be ordered by distance, with the first result being the post published at the nearest location.
-This order can be overridden by the `order` parameter.
-
-To limit the maximum distance, you can use `$maxDistanceInMiles`, `$maxDistanceInKilometers`, or `$maxDistanceInRadians`. For example, to limit the distance to 10 miles:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'where={
- "location": {
- "$nearSphere": {
- "__type": "GeoPoint",
- "latitude": 39.9,
- "longitude": 116.4
- },
- "$maxDistanceInMiles": 10.0
- }
- }' \
- https://{{host}}/1.1/classes/Post
-```
-
-You can also query for objects within a rectangular area with this format: `{"$within": {"$box": [southwestGeoPoint, northeastGeoPoint]}}`.
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'where={
- "location": {
- "$within": {
- "$box": [
- {
- "__type": "GeoPoint",
- "latitude": 39.97,
- "longitude": 116.33
- },
- {
- "__type": "GeoPoint",
- "latitude": 39.99,
- "longitude": 116.37
- }
- ]
- }
- }
- }' \
- https://{{host}}/1.1/classes/Post
-```
-
-Be aware that the range of `latitude` is `[-90.0, 90.0]`, and the range of `longitude` is `[-180.0, 180.0]`.
-There is currently one limit on GeoPoints: every class can only contain one GeoPoint attribute.
-
-### File Queries
-
-Querying files is similar to querying normal objects.
-For example, to query all files (just like querying normal objects, it returns at most 100 results by default):
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- https://{{host}}/1.1/classes/files
-```
-
-Be aware that the `url`s of the internal files (those uploaded to the cloud) are automatically generated by the cloud and are applied with the logic related to updating custom domains.
-This means that you should only query external files (those saved with URLs) with the `url` field. For internal files, please query with the `key` field (the path in the URL) instead.
-
-For the same reason, when iterating over files with `scan`, the internal files in the result will not have the `url` field but only the `key` field.
-
-### Counting Results
-
-You can pass `count=1` parameter to retrieve the count of matched results.
-For example, if you just need to know how many posts a specific user has made:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'where={"pubUser":"TapTap"}' \
- --data-urlencode 'count=1' \
- --data-urlencode 'limit=0' \
- https://{{host}}/1.1/classes/Post
-```
-
-Since `limit=0`, only the `count` will be returned, and the `results` array will be empty.
-
-```json
-{
- "results": [],
- "count": 7
-}
-```
-
-Given a nonzero `limit` parameter, results will be returned together with the count.
-
-### Compound Queries
-
-You can use the `$or` operator to query objects matching **any one of the several queries**. For example, to query posts made by official accounts and personal accounts:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'where={"$or":[{"pubUserCertificate":{"$gt":2}},{"pubUserCertificate":{"$lt":3}}]}' \
- https://{{host}}/1.1/classes/Post
-```
-
-Similarly, you can use `$and` operator to query objects **matching all subqueries**. For example, to query all the objects that have the `price` field and with `price` not equaling to `199`:
-
-```
-where={"$and":[{"price": {"$ne":199}},{"price":{"$exists":true}}]}
-```
-
-The query condition expressions are implicitly combined with the `$and` operator, so the query expression above could also be rewritten as:
-
-```
-where=[{"price": {"$ne":199}},{"price":{"$exists":true}}]
-```
-
-In fact, since both conditions are targeted at the same field (`price`), the above query expression can be further simplified to:
-
-```
-where={"price": {"$ne":199, "$exists":true}}
-```
-
-However, to combine two or more OR-ed queries, you have to use the `$and` operator:
-
-```
-where={"$and":[{"$or":[{"pubUserCertificate":{"$gt":2}},{"pubUserCertificate":{"$lt":3}}]},{"$or":[{"pubUser":"TapTap"},{"pubUser":"TDS"}]}]}
-```
-
-Be aware that **non-filtering constraints such as `limit`, `skip`, `order`, and `include` are not allowed in subqueries of a compound query**.
-
-## Users
-
-With the users API, you can build an account system for your application quickly and conveniently.
-
-Users (the `_User` class) share many traits with other classes. For example, `_User` is schema-free as well.
-However, all user objects must have `username` and `password` attributes. `password` will be encrypted automatically.
-`username` and `email` (if available) attributes must be unique (case sensitive).
-
-### Signing Up
-
-To create a new user:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{"username":"tom","password":"f32@ds*@&dsa","phone":"18612340000"}' \
- https://{{host}}/1.1/users
-```
-
-As mentioned above, `username` and `password` are required. `password` will be stored in encrypted form, and the cloud will never return its value to the client side.
-
-If the registration succeeds, the cloud will return `201 Created` and the `Location` will contain the URL for that user:
-
-```sh
-Status: 201 Created
-Location: https://{{host}}/1.1/users/55a47496e4b05001a7732c5f
-```
-
-The response body is a JSON object containing three attributes:
-
-```json
-{
- "sessionToken": "qmdj8pdidnmyzp0c7yqil91oc",
- "createdAt": "2015-07-14T02:31:50.100Z",
- "objectId": "55a47496e4b05001a7732c5f"
-}
-```
-
-### Logging In
-
-To log in with username and password:
-
-```sh
-curl -X POST \
--H "Content-Type: application/json" \
--H "X-LC-Id: {{appid}}" \
--H "X-LC-Key: {{appkey}}" \
--d '{"username":"tom","password":"f32@ds*@&dsa"}' \
-https://{{host}}/1.1/login
-```
-
-You can also log in with email and password by replacing the body with:
-
-```json
-{ "email": "tom@example.com", "password": "f32@ds*@&dsa" }
-```
-
-Or, log in with phone number and password:
-
-```json
-{ "mobilePhoneNumber": "+86186xxxxxxxx", "password": "f32@ds*@&dsa" }
-```
-
-The response body is a JSON object containing all the attributes of that user, except `password`:
-
-```json
-{
- "sessionToken": "qmdj8pdidnmyzp0c7yqil91oc",
- "updatedAt": "2015-07-14T02:31:50.100Z",
- "phone": "18612340000",
- "objectId": "55a47496e4b05001a7732c5f",
- "username": "tom",
- "createdAt": "2015-07-14T02:31:50.100Z",
- "emailVerified": false,
- "mobilePhoneVerified": false
-}
-```
-
-### Refresh sessionToken
-
-To refresh a user's `sessionToken`:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: qmdj8pdidnmyzp0c7yqil91oc" \
- https://{{host}}/1.1/users/57e3bcca67f35600577c3063/refreshSessionToken
-```
-
-`X-LC-Session` can be omitted when using Master Key.
-
-If succeeded, a new `sessionToken` will be returned, with user information:
-
-```json
-{
- "sessionToken": "5frlikqlwzx1nh3wzsdtfr4q7",
- "updatedAt": "2016-10-20T03:10:57.926Z",
- "objectId": "57e3bcca67f35600577c3063",
- "username": "tom",
- "createdAt": "2016-09-22T11:13:14.842Z",
- "emailVerified": false,
- "mobilePhoneVerified": false
-}
-```
-
-#### Locking Users
-
-Seven consecutive failed login attempts for a user within 15 minutes will trigger a lock.
-Once this happens, the cloud will return the following error:
-
-```json
-{
- "code": 219,
- "error": "Tried too many times to signin."
-}
-```
-
-The cloud will release this lock automatically in 15 minutes after the last login failure.
-You cannot adjust this behavior via SDK or REST API.
-During the locking period, the user is not allowed to log in,
-even if they provide the correct password.
-This restriction also applies to SDK and Cloud Engine.
-
-### Verifying Email Address
-
-Once a user clicked the verification link in the email, their `emailVerified` will be set to `true`.
-
-`emailVerified` is a Boolean with 3 statuses:
-
-1. `true`: the user has verified their email address via clicking the link in the verification mail.
-2. `false`: when a user's `email` attribute is set or modified, the cloud will set their `emailVerified` to `false` and send a verification email to the user. After the user clicks the verification link in the email, the cloud will set `emailVerified` to `true`.
-3. `null`: The user does not have an `email`, or the user object is created when the verifying new user's email address option is disabled.
-
-The verification link expires in one week.
-To resend the verification email:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{"email":"tom@example.com"}' \
- https://{{host}}/1.1/requestEmailVerify
-```
-
-### Resetting Password
-
-A user can reset their password via email:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{"email":"tom@example.com"}' \
- https://{{host}}/1.1/requestPasswordReset
-```
-
-If succeed, the response body will be an empty JSON object:
-
-```json
-{}
-```
-
-### Retrieving Users
-
-To retrieve a user, you can send a GET request to the user URL (as in the `Location` header returned on [successful signing up](#signing-up)).
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- https://{{host}}/1.1/users/55a47496e4b05001a7732c5f
-```
-
-Alternatively, you can retrieve a user via their `sessionToken`:
-
-```
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: qmdj8pdidnmyzp0c7yqil91oc" \
- https://{{host}}/1.1/users/me
-```
-
-The returned JSON object is the same as in [`/login`](#logging-in).
-
-If the user does not exist, a `400 Bad Request` will be returned:
-
-```json
-{
- "code": 211,
- "error": "Could not find user."
-}
-```
-
-### Updating Users
-
-Similar to [Updating Objects](#updating-objects), you can send a `PUT` request to the user URL to update a user's data.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: qmdj8pdidnmyzp0c7yqil91oc" \
- -H "Content-Type: application/json" \
- -d '{"phone":"18600001234"}' \
- https://{{host}}/1.1/users/55a47496e4b05001a7732c5f
-```
-
-The `X-LC-Session` HTTP header is to authenticate the modification,
-whose value is the user's `sessionToken`.
-
-If succeed, `updatedAt` will be returned.
-This is the same as [Updating Objects](#updating-objects).
-
-If you want to update `username`, then you have to ensure that the new value of `username` must not conflict with other existing users.
-
-If you want to update `password` after verifying the old password,
-you can use `PUT /1.1/users/:objectId/updatePassword` instead.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: qmdj8pdidnmyzp0c7yqil91oc" \
- -H "Content-Type: application/json" \
- -d '{"old_password":"the_old_password", "new_password":"the_new_password"}' \
- https://{{host}}/1.1/users/55a47496e4b05001a7732c5f/updatePassword
-```
-
-Note that this API still requires the `X-LC-Session` header.
-
-### Querying Users
-
-You can query users like [how you query regular objects](#queries) by sending `GET` requests to `/1.1/users`.
-
-However, for security concerns, all queries on users will be rejected by the cloud unless you use the master key or have properly configured the `_User` class' ACL settings.
-
-### Deleting Users
-
-Just like deleting an object, you can send a `DELETE` request to delete a user.
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: qmdj8pdidnmyzp0c7yqil91oc" \
- https://{{host}}/1.1/users/55a47496e4b05001a7732c5f
-```
-
-The `X-LC-Session` HTTP header is used for authenticating this request.
-
-### Linking Users
-
-To allow users to use third-party accounts to log in to your application, you can use the `authData` attribute of users.
-
-`authData` is a JSON object whose schema may be different for different services.
-
-The simplest form of `authData` is as follows:
-
-```json
-{
- "anonymous": {
- "id": "random UUID with lowercase hexadecimal digits"
- // other optional keys
- }
-}
-```
-
-This is used for anonymous users,
-for example, to provide a "try it before signing up" or "guest login" feature for your application.
-
-The `authData` for an arbitrary platform:
-
-```json
-{
- "platform_name": {
- "uid": "unique user id on that platform (string)",
- "access_token": "access token for the user"
- // other optional keys
- }
-}
-```
-
-`authData` can have other additional keys, but it must contain both `uid` and `access_token`.
-The cloud will automatically create a unique index for `authData.platform_name.uid`.
-This avoids binding a third-party account to multiple users.
-However, you need to verify `authData` yourself (except for certain platforms, see below).
-
-Example `authData` objects:
-
-[Apple](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api):
-
-```json
-{
- "authData": {
- "lc_apple": {
- "uid": "user identifier",
- "identity_token": "identity token",
- "code": "authorization code"
- }
- }
-}
-```
-
-[TapTap](https://www.taptap.io/):
-
-```json
-{
- "taptap": {
- "kid": "mac_key id",
- "access_token": "Same as kid",
- "token_type": "mac",
- "mac_key": "mac key",
- "mac_algorithm": "hmac-sha-1",
- "openid": "The unique identifier of the user; a user has different openid's for different apps",
- "name": "username",
- "avatar": "URL of the user's avatar",
- "unionid": "The unique identifier of the user; a user has the same unionid for all the apps under the same developer"
- }
-}
-```
-
-Other platforms:
-
-```json
-{
- "platform name, like facebook": {
- "uid": "A unique identifier of the user from the platform",
- "access_token": "Access Token"
- // ……optional properties
- }
-}
-```
-
-#### Third-Party Signing Up and Login
-
-To sign up or log in via a third party account, you also send a POST request with the `authData`.
-For example, to log in with Apple:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "authData": {
- "lc_apple": {
- "uid": "user identifier",
- "identity_token": "identity token",
- "code": "authorization code"
- }
- }
- }' \
- https://{{host}}/1.1/users
-```
-
-The response body will be a JSON object whose content is similar to the one returned when creating or logging in as a regular user.
-A new user will be automatically assigned with a random username, e.g., `ec9m07bo32cko6soqtvn6bko5`.
-
-#### Linking a Third-Party Account
-
-To link a third-party account to an existing user,
-just update this user's `authData` attribute.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: qmdj8pdidnmyzp0c7yqil91oc" \
- -H "Content-Type: application/json" \
- -d '{
- "lc_apple": {
- "uid": "user identifier",
- "identity_token": "identity token",
- "code": "authorization code"
- }
- }' \
- https://{{host}}/1.1/users/55a47496e4b05001a7732c5f
-```
-
-This user can be authenticated via matching `authData` afterward.
-
-#### Unlinking a Third-Party Account
-
-Similarly, to unlink a user from a third party account,
-just delete the platform in their `authData` attribute.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: 6fehqhr2t2na5mv1aq2om7jgz" \
- -H "Content-Type: application/json" \
- -d '{"authData.lc_apple":{"__op":"Delete"}}' \
- https://{{host}}/1.1/users/5b7e53a767f356005fb374f6
-```
-
-## Roles
-
-The Data Storage service has a preserved class `_Role` for roles.
-
-For security concerns, roles are typically created and managed manually or via a separate management interface, not directly in your app.
-
-### Creating Roles
-
-Creating a role is similar to creating an object, except that you must specify the `name` and `ACL` attributes. To prevent allowing wrong users to modify a role accidentally, you should set a restrictive and rigid `ACL`.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "name": "Manager",
- "ACL": {
- "*": {
- "read": true
- }
- }
- }' \
- https://{{host}}/1.1/roles
-```
-
-The response is the same as creating an object.
-
-To create a role with existing child roles and users:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "name": "CLevel",
- "ACL": {
- "*": {
- "read": true
- }
- },
- "roles": {
- "__op": "AddRelation",
- "objects": [
- {
- "__type": "Pointer",
- "className": "_Role",
- "objectId": "55a48351e4b05001a774a89f"
- }
- ]
- },
- "users": {
- "__op": "AddRelation",
- "objects": [
- {
- "__type": "Pointer",
- "className": "_User",
- "objectId": "55a47496e4b05001a7732c5f"
- }
- ]
- }
- }' \
- https://{{host}}/1.1/roles
-```
-
-You may have noticed that there is a new operator `AddRelation` we have not seen before.
-This operator adds a Relation to an object.
-The actual implementation of Relation is quite complicated for performance issues,
-but conceptually you can consider a Relation as an array of pointers, and they are only used in roles.
-
-### Retrieving Roles
-
-Retrieving a role is similar to retrieving an object:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- https://{{host}}/1.1/roles/55a483f0e4b05001a774b837
-```
-
-The response body will be a JSON object:
-
-```json
-{
- "name": "CLevel",
- "createdAt": "2015-07-14T03:37:20.992Z",
- "updatedAt": "2015-07-14T03:37:20.994Z",
- "objectId": "55a483f0e4b05001a774b837",
- "users": {
- "__type": "Relation",
- "className": "_User"
- },
- "roles": {
- "__type": "Relation",
- "className": "_Role"
- }
-}
-```
-
-### Updating Roles
-
-Updating roles are similar to updating objects, except that `name` cannot be modified, as mentioned above.
-To add or remove users and child roles, you can use `AddRelation` and `RemoveRelation` operators.
-
-Suppose we have a `Manager` role with objectId `55a48351e4b05001a774a89f`, we can add a user to it as below:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "users": {
- "__op": "AddRelation",
- "objects": [
- {
- "__type": "Pointer",
- "className": "_User",
- "objectId": "55a4800fe4b05001a7745c41"
- }
- ]
- }
- }' \
- https://{{host}}/1.1/roles/55a48351e4b05001a774a89f
-```
-
-Similarly, to remove a child role:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "roles": {
- "__op": "RemoveRelation",
- "objects": [
- {
- "__type": "Pointer",
- "className": "_Role",
- "objectId": "55a483f0e4b05001a774b837"
- }
- ]
- }
- }' \
- https://{{host}}/1.1/roles/55a48351e4b05001a774a89f
-```
-
-### Querying Roles
-
-To find the roles a user belongs to:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode 'where={"users": {"__type": "Pointer", "className": "_User", "objectId": "5e03100ed4b56c008e4a91dc"}}' \
- https://{{host}}/1.1/roles
-```
-
-To find the users contained in a role (users contained in sub-roles not counted):
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -G \
- --data-urlencode '"$relatedTo":{"object":{"__type":"Pointer","className":"_Role","objectId":"5f3dea7b7a53400006b13886"},"key":"users"}' \
- https://{{host}}/1.1/users
-```
-
-You can also query roles based on other attributes, just like querying a normal object.
-
-### Deleting Roles
-
-Deleting roles is similar to delete objects.
-It is authenticated with the `X-LC-Session` HTTP header.
-The session token passed in must belong to a user who has the permission to delete the specified role.
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: qmdj8pdidnmyzp0c7yqil91oc" \
- https://{{host}}/1.1/roles/55a483f0e4b05001a774b837
-```
-
-### Roles and ACL
-
-As demonstrated above, accessing data via REST API is also restricted by ACL, just as SDKs.
-
-Roles make maintaining ACL easier.
-For example, to set an ACL of an object with the following permissions:
-
-- It can be read by `Staff`s.
-- It can only be written by `Manager`s and its creator.
-
-```json
-{
- "55a4800fe4b05001a7745c41": {
- "write": true
- },
- "role:Staff": {
- "read": true
- },
- "role:Manager": {
- "write": true
- }
-}
-```
-
-The creator belongs to the `Staff` role, and the `Manager` role is a child role of the `Staff` role.
-Therefore, since they will inherit read permissions, we did not grant them the read permission manually.
-
-Let's look at another example of permission inherence among roles.
-In UGC applications such as forums, `Administrators` typically have all the permissions of `Moderators`.
-Thus `Administrators` should be a sub-role of `Moderators`.
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "roles": {
- "__op": "AddRelation",
- "objects": [
- {
- "__type": "Pointer",
- "className": "_Role",
- "objectId": ""
- }
- ]
- }
- }' \
- https://{{host}}/1.1/roles/
-```
-
-## Files
-
-### Creating Files
-
-The REST API does not support uploading files. Please use an SDK or the CLI to upload files.
-
-If you already have a URL for a file, you can create a file by adding an entry like this:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{"url": "https://example.com/foo.jpg", "name": "foo.jpg", "mime_type": "image/jpeg"}' \
- https://{{host}}/1.1/files
-```
-
-### Associating With Objects
-
-As mentioned above, files can be considered as a special form of pointers.
-To associate a file object with an object, we just pass the file object `{"id": "objectId of the file", "__type": "File"}` to an attribute of that file.
-For example, to create a `Staff` object with a photo:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "name": "tom",
- "picture": {
- "id": "543cbaede4b07db196f50f3c",
- "__type": "File"
- }
- }' \
- https://{{host}}/1.1/classes/Staff
-```
-
-Here `id` is the objectId of the file.
-
-### Deleting Files
-
-Deleting files is similar to deleting objects:
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- https://{{host}}/1.1/files/543cbaede4b07db196f50f3c
-```
-
-## Schema
-
-You can use REST API to fetch the data schema of your application.
-For security concerns, the master key is required to fetch data schema.
-
-To fetch the schema of all classes:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.1/schemas
-```
-
-Result:
-
-```json
-{
- "_User": {
- "username": { "type": "String" },
- "password": { "type": "String" },
- "objectId": { "type": "String" },
- "emailVerified": { "type": "Boolean" },
- "email": { "type": "String" },
- "createdAt": { "type": "Date" },
- "updatedAt": { "type": "Date" },
- "authData": { "type": "Object" }
- }
- // other classes
-}
-```
-
-You can also fetch a single class's schema:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.1/schemas/_User
-```
-
-Data schema can be used with tools such as code generators and internal management interfaces.
-
-## Exporting Your Data
-
-For security concerns, master key is required to export your data:
-
-```
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{}' \
- https://{{host}}/1.1/exportData
-```
-
-To specify date range (`updatedAt`) of data to export:
-
-```
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"from_date":"2015-09-20", "to_date":"2015-09-25"}' \
- https://{{host}}/1.1/exportData
-```
-
-To specify classes of data to export:
-
-```
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"classes":"_User,GameScore,Post"}' \
- https://{{host}}/1.1/exportData
-```
-
-Just export the schema (no data will be exported):
-
-```
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"only-schema":"true"}' \
- https://{{host}}/1.1/exportData
-```
-
-The exported schema file can be imported into other applications via the import data function on the dashboard.
-
-After the data is exported, we will send an email to the application creator, containing the URL to download the data.
-You can also specify the address to receive this email:
-
-```
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{"email":"username@exmaple.com"}' \
- https://{{host}}/1.1/exportData
-```
-
-The export data job id will be returned:
-
-```json
-{
- "status": "running",
- "id": "1wugzx81LvS5R4RHsuaeMPKlJqFMFyLwYDNcx6LvCc6MEzQ2",
- "app_id": "{{appid}}"
-}
-```
-
-You can also query the export data job status via the id returned previously:
-
-```
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.1/exportData/1wugzx81LvS5R4RHsuaeMPKlJqFMFyLwYDNcx6LvCc6MEzQ2
-```
-
-If the job status is `done`, the download url will also be returned:
-
-```json
-{
- "status": "done",
- "download_url": "https://download.leancloud.cn/export/example.tar.gz",
- "id": "1wugzx81LvS5R4RHsuaeMPKlJqFMFyLwYDNcx6LvCc6MEzQ2",
- "app_id": "{{appid}}"
-}
-```
-
-If the job status is still `running`, you can query it again later.
-
-## Other
-
-### Server Time
-
-To retrieve the server's current time:
-
-```
-curl -i -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- https://{{host}}/1.1/date
-```
-
-The returned date is in UTC:
-
-```json
-{
- "iso": "2015-08-27T07:38:33.643Z",
- "__type": "Date"
-}
-```
-
-## CORS Workarounds
-
-You can wrap GET, PUT, and DELETE requests in a POST request:
-
-- Specify the intended HTTP method in the `_method` parameter.
-- Specify `appid` and `appkey` in `_ApplicationId` and `_ApplicationKey` parameters.
-
-This is a workaround that only works for certain platforms.
-It is recommended to follow the HTML CORS standard instead.
-
-### GET
-
-```
- curl -i -X POST \
- -H "Content-Type: text/plain" \
- -d \
- '{"_method":"GET",
- "_ApplicationId":"{{appid}}",
- "_ApplicationKey":"{{appkey}}"}' \
- https://{{host}}/1.1/classes/Post/
-```
-
-### PUT
-
-```
-curl -i -X POST \
- -H "Content-Type: text/plain" \
- -d \
- '{"_method":"PUT",
- "_ApplicationId":"{{appid}}",
- "_ApplicationKey":"{{appkey}}",
- "upvotes":99}' \
- https://{{host}}/1.1/classes/Post/
-```
-
-### DELETE
-
-```
-curl -i -X POST \
- -H "Content-Type: text/plain" \
- -d \
- '{"_method": "DELETE",
- "_ApplicationId":"{{appid}}",
- "_ApplicationKey":"{{appkey}}"}' \
- https://{{host}}/1.1/classes/Post/
-```
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/swift-guide/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/swift-guide/_category_.json
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/swift-guide/_category_.json
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/swift-guide/_category_.json
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/swift-guide/setup-swift.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/swift-guide/setup-swift.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/swift-guide/setup-swift.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/swift-guide/setup-swift.mdx
diff --git a/leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/swift-guide/swift.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/swift-guide/swift.mdx
similarity index 100%
rename from leancloud/i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/swift-guide/swift.mdx
rename to i18n/en/docusaurus-plugin-content-docs/current/sdk/storage/swift-guide/swift.mdx
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/_category_.json
deleted file mode 100644
index 2b9e010f5..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "TapSupport",
- "collapsed": true,
- "position": 21
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/features.mdx
deleted file mode 100644
index 1b5d0d4fe..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/features.mdx
+++ /dev/null
@@ -1,52 +0,0 @@
----
-title: TapSupport Introduction
-sidebar_label: Introduction
-sidebar_position: 1
----
-
-TapSupport assists the game operation teams in solving problems encountered by players faster and better.
-
-## Key Benefits
-
-### Rich Tools to Improve Customer Support Efficiency
-
-TapSupport consists of two core systems: Ticket and Knowledge Base, providing a set of freely combinable functional modules, including announcements, FAQs, forms, etc., to solve players' problems and requests.
-
-- The ticket module comes with the category-based auto-assignment function. You can also customize triggers to achieve a more complex assignment mechanism that can quickly assign tickets to the most appropriate staff members.
-
-- Tickets support custom fields that can be filled in by the player or the developer via a form, providing detailed context for the staff.
-
-- Multiple notification methods are supported to ensure you don't miss any player responses.
-
-
-### Multi-dimensional Analytics to Support Operations
-
-TapSupport is designed to help solve player problems, but it is also designed to help the operation team identify issues, refine valuable content, and improve the game itself.
-
-- The ticket module has flexible search and filtering capabilities and support aggregation and analysis by category, field, and status. It also supports data export for further analysis with third-party tools.
-
-- Users can leave feedback on knowledge base articles so that you can see behind the scenes what content is effective and what is not solving the player's problem.
-
-
-### Simple and Powerful Management Features
-
-TapSupport provides an intuitive role-based permission control system that supports custom roles in addition to pre-built roles for more flexible needs.
-
-The system provides managers with detailed data to evaluate the quantity and quality of customer support work, including but not limited to the number of tickets handled, the timeliness of responses to players, and player satisfaction.
-
-### User Experience Designed for Players
-
-As a customer support tool focused on gaming scenarios, TapSupport provides an in-game customer support experience. Players don't have to switch apps to use the TapSupport feature, and they can receive in-game prompts immediately after customer support replies.
-
-In addition, the SDK optimizes visuals and interactions for landscape scenarios and provides developers with a control API to reduce the impact of the SDK on the performance of the game's core processes.
-
-### Out-of-the-box Development Experience
-
-The TapSupport client SDK covers popular game development platforms and provides features such as user interface, login, data reporting, and badge maintenance. The highly integrated features provide an out-of-the-box development experience.
-
-TapSupport supports multiple login methods. If the game is already using the TDS built-in account, you can log in directly by using it. It also supports logging in with the game's own account system.
-
-
-:::info Still under development
-TapSupport is currently in public beta, and some features that are still in development may not be available to all users.
-:::
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/features/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/features/_category_.json
deleted file mode 100644
index ffebf8e89..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/features/_category_.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "label": "Guide",
- "position": 2
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/features/roles.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/features/roles.mdx
deleted file mode 100644
index 8f341dc1f..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/features/roles.mdx
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: TapSupport Authority Control
-sidebar_label: Customer Service Authorities
-sidebar_position: 2
----
-
-The Customer Service module has a separate authority system from the Developer Center section. Authorities consist of two parts: role and scope.
-
-- Role: determines which functions and actions a user can use;
-- Scope: determines which work orders a user can access.
-
-### Roles
-
-User roles are categorized into the following categories according to their different responsibilities:
-
-| user role | remit | access path | billing |
-| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ---------- |
-| Super Administrator | Maintenance services and security-related configurations | Developer Center | not billed |
-| Administrator | daily management Has all configuration permissions except for service and security related configurations | Customer Service Workbench | billing |
-| Customer Service | Communicate directly with players and handle player requests | Customer Service Workbench | billing |
-
-Super Administrators are Tap users with TapSupport authorities in the Developer Center. Users in other roles are independent users of the customer service system who log in to use the TapSupport module through the TapSupport Workbench, which is separate from the Developer Center, and are collectively referred to as "Members". Members can be added by super administrators in the Developer Center or by administrators in the TapSupport Workbench. A member can have one or more user roles, and super administrators or administrators can modify the roles of members.
-
-The detailed correspondence between roles and permissions is as follows:
-
-| | | Super Administrator | Administrator | Customer Services | Collaborators | Developers |
-| --------------------------- | ---------------------------------------------------------------------------- | :----------------------------: | :----------------------------: | :--: | :----: | :-------------------------: |
-| Services and Security | Enabling, disabling services | ✔️ | | | | |
-| | Configuring a custom domain name | ✔️ | | | | |
-| User Management | Querying member list and information | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
-| | Managing members Adding, disabling, and modifying profiles | ✔️ | ✔️ | | | |
-| | Checking Player Profile | | ✔️ | ✔️ | ✔️ | ✔️ |
-| | Managing Players Adding and modifying information | | ✔️ | ✔️ | | |
-| Tickets | Access to Tickets - Inquiring and viewing tickets - Submitting an internal message | | ✔️ | ✔️ | ✔️ | ✔️ |
-| | Modifying Tickets Properties | | ✔️ | ✔️ | | |
-| | Replying to the player | | | ✔️ | | |
-| Knowledge Base | Checking up | | ✔️ | ✔️ | ✔️ | ✔️ |
-| | Management Adding, modifying content and posting status | | ✔️ | ✔️ | | |
-| Setting | Content Setting - Products and Classification - Fields and forms of Tickets - Dynamic content | | ✔️ | | | ✔️ read only |
-| | TapSupport Settings - Cluster - Trigger - Notification channel management | | ✔️ | | | |
-| | Development Settings - Player Authentication Methods - Customized domain names (read only) | | ✔️ | | | ✔️ |
-| Audits | Audit log | ✔️ Manufacturers' section | ✔️ Membership component | | | |
-
-### Scope
-
-If a user has the "Access Tickets" permission, there are three different scopes of work orders that he/she can access as follows:
-
-- All Tickets
-- Only Tickets for their group
-- Only Tickets assigned to this user only
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/features/setup.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/features/setup.mdx
deleted file mode 100644
index c7b7c157d..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/features/setup.mdx
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: Open TapSupport Privilege
-sidebar_label: Opening process
-sidebar_position: 1
----
-
-To use TapSupport Service, you need to activate it in the TapTap Developer Center.
-
-### Enable Service
-
-TapSupport Service is a vendor dimension service, developers can self-enable the service at **Developer Center Home Page > Game Service > TapSupport Service**.
-
-:::info Permission Requirements
-Access to the TapSupport Service module in the Developer Center requires the **Customer Service Administrator** permission in the vendor permissions.
-:::
-
-### Price Plan
-
-The TapSupport system is charged according to the number of activated customers.
-
-:::info
-There is no billing during the beta period, we will post a notice in advance before the official billing.
-:::
-
-### Add Customer Service
-
-After enabling customer service, you first need to add customer service.
-
-You can switch to **Customer Service Management** tab to add customer service. To add customer service, you need to provide the mobile phone number of the customer service, and the customer service will receive an invitation SMS after you add the customer service. Customer service logs in to the customer service backend by adding the SMS verification code via mobile phone.
-
-The SMS received by the customer service will have a link to the customer service workbench, you can also find the customer service workbench in the upper right corner of the game customer service module in the developer center to jump to the entrance.
-
-When adding customer service, you need to specify the role and scope of the customer service, the responsibilities and specific authorities of different roles see ["Authority Control of Customer Service"](/sdk/tap-support/features/roles/).
-
-:::info Be the first customer service!
-Super administrators can only perform account and security related configurations in the DC backend, specific customer service business-related configurations need to be performed in the Customer Service Workbench. To configure the service, you can add yourself as an administrator.
-:::
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/guide.mdx
deleted file mode 100644
index 986ab6df9..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tap-support/guide.mdx
+++ /dev/null
@@ -1,906 +0,0 @@
----
-title: Tap Customer Service Development Guide
-sidebar_label: Guide
-sidebar_position: 3
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-import UnitySDKInstallation from "../_partials/unity-sdk-installation.mdx";
-
-This article describes how to access the customer service system provided by TDS in the game.
-
-## SDK initialization
-
-Get TapSDK at [download page](/tap-download) and introduce `TapSupport` module:
-
-
-<>
-
-
-
->
-<>
-
-The TapSupport SDK relies on the TapCommon module:
-
-
- {`repositories{
- flatDir {
- dirs 'libs'
- }
-}
-dependencies {
- ...
- implementation name:'TapCommon_${sdkVersions.taptap.android}', ext:'aar'
- implementation (name:'TapSupport_${sdkVersions.taptap.android}', ext:'aar')
-}`}
-
-
-Verify that network permissions have been added to `AndroidManifest.xml`:
-
-```java
-
-```
-
->
-<>
-
-The TapSupport SDK relies on the TapCommon module:
-
-```objc
-TapCommonSDK.framework
-TapSupportSDK.framework
-```
-
->
-
-<>
-
-The TapSupport SDK relies on the TapCommon module:
-
-```csharp
-PublicDependencyModuleNames.AddRange(
- new string[]
- {
- "TapCommon",
- "TapSupport"
- // ... add other public dependencies that you statically link with here ...
- }
- );
-```
-
->
-
-
-
-Before initializing the SDK, there are a few preparations to complete:
-
-1. Ask the vendor administrator or customer service administrator to invite you to become a customer service administrator.
-2. Each vendor will be assigned a unique domain name, which you need to write down and use. You can find the available domain names in **Settings > Developer Information** in the Customer Service Workbench. If you want to use your own domain name, you can contact the vendor administrator to bind it in the Game Customer Service module of the Developer Center.
-3. In the Customer Service Workbench, create a product for your game (**Settings > Administration > Products & Categories**) and write down the ID of this product.
-
-Then use the following code to initialize the TapSupport module:
-
-
-
-```cs
-using TapTap.Support;
-
-TapSupport.Init("https://please-replace-with-your-customized.domain.com", "Product ID");
-```
-
-```java
-import com.tds.tapsupport.TapSupport;
-import com.tds.tapsupport.TapSupportCallback;
-import com.tds.tapsupport.TapSupportConfig;
-
-TapSupportConfig config = new TapSupportConfig("https://please-replace-with-your-customized.domain.com", "Product ID", new TapSupportCallback() {
- @Override
- public void onUnreadStatusChanged(boolean hasUnread) {
- // We'll discuss the use of callbacks in § Unread message notifications.
- }
-})
-TapSupport.setConfig(this, config);
-```
-
-```objc
-#import
-
-TapSupportConfig *config = [TapSupportConfig new];
-config.server = @"https://please-replace-with-your-customized.domain.com";
-config.productID = @"Product ID";
-config.callback = self;
-[TapSupport shareInstance].config = config;
-```
-
-```cpp
-#include "TapUESupport.h"
-
-FTapSupportConfig Config;
-Config.ServerUrl = TEXT("https://please-replace-with-your-customized.domain.com");
-Config.ProductID = TEXT("Product ID");
-TapUESupport::Init(Config);
-```
-
-
-
-In the above code example, `please-replace-with-your-customized.domain.com` is the domain name obtained or bound in Preparation 2. The `product ID` is the ID of the new product created in Preparation 3.
-
-## Login
-
-In order to ensure that the information submitted by a player, such as forms, is only accessible to that player, the customer service system requires a login in order to be used. We offer three different login options:
-
-- TDSUser (TDS Built-in Account) Login
-- TDSUser login
-- Anonymous Login
-
-:::note
-Logging in here refers to the process of authentication when a gamer uses the customer service system's features within the game. This step is the interaction between the game side and the customer service system, and is usually not perceived by the player, as opposed to logging in to the game itself (e.g., the anonymous login here has nothing to do with the game's guest login).
-:::
-
-### TDS Built-in Account Login
-
-If your game uses the TDS built-in account service, you can log in to the customer service system directly on the client side using the logged-in TDSUser authorization.
-
-:::info
-The TDS Built-in Account Service is a user system provided by TDS that supports multiple login methods. If your game is already using TapTap Login, Friends, Achievements, Leaderboards and other services, you are probably already using TDS Built-in Accounts. For more information, please refer to ["Introduction to Built-in Account Features"](/sdk/authentication/features/).
-:::
-
-To use TDSUser authorization to log in to the customer service system, you first request an authorization token using a logged-in TDS user account, and then use that token to call the TDS login interface of the customer service module:
-
-
-
-```cs
-try {
- string token = await TDSUser.RetrieveShortToken();
- await TapSupport.LoginWithTDSCredential(token);
- Debug.Log("Log in to TDSUser JWT Completion");
-} catch (TapException e) {
- Debug.LogError($"{e.Code} : {e.Message}");
-} catch (Exception e) {
- Debug.Log(e);
-}
-```
-
-```java
-TDSUser.retrieveShortTokenInBackground(tdsUser.getSessionToken()).subscribe(new Observer() {
- @Override
- public void onSubscribe(Disposable d) {
-
- }
-
- @Override
- public void onNext(JSONObject jsonObject) {
- String credential = jsonObject.getString("identityToken");
- TapSupport.loginWithTDSCredential(credential, new TapSupport.LoginCallback() {
- @Override
- public void onComplete(boolean success, Throwable error) {
- if (success) {
- // Login Successful
- } else {
- // Login Failure
- Log.e("TapSupportActivity", "login:error:" + error.toString());
- }
- }
- });
- }
-
- @Override
- public void onError(Throwable error) {
- // Failed to request authorization token
- }
-
- @Override
- public void onComplete() {
-
- }
-});
-```
-
-```objc
-[TDSUser retrieveShortTokenWithCallback:^(NSString *_Nullable token, NSError *_Nullable error) {
- if (error) {
- // Failed to request authorization token
- } else {
- [TapSupport loginWithTDSCredential:token
- handler:^(BOOL succcess, NSError *_Nullable error) {
- if (succcess) {
- // Successful login
- } else {
- // Login Failure
- }
- }];
- }
- }];
-```
-
-```cpp
-
-// Todo 1
-
-if (TSharedPtr User = FTDSUser::GetCurrentUser())
-{
- User->RetrieveShortToken(
- FStringSignature::CreateLambda([](const FString& Credential)
- {
- /** Getting Credential Success */
- TapUESupport::LoginWithTDSCredential(
- Credential,
- FSimpleDelegate::CreateLambda([]()
- {
- /*** Login Successful */
- }),
- FTUError::FDelegate::CreateLambda([](const FTUError& Error)
- {
- /*** Login Failure */
- }));
- }),
- FLCError::FDelegate::CreateLambda([](const FLCError& Error)
- {
- /** Failed to get Credential */
- }));
-}
-
-```
-
-
-
-#### Exception Handling
-
-If the SDK throws an exception during login and subsequent processes, developers can be notified via a callback.
-
-Among these exceptions, we need to handle the token expiration (`EXPIRED_CREDENTIAL`) exception in particular, and re-execute the above `request authorization token, call login interface` process to log in the customer service again.
-
-For other kinds of exceptions, it is recommended to show them to players directly. Since customer service exceptions usually don't affect the player's playing experience, we can consider only prompting the player that customer service is not available at the customer service portal, and provide the interaction of manually triggering retries.
-
-
-
-```cs
-
-if (e is TapException ex && ex.Code == 9006) {
- // Login expired
- } else {
- // Other exceptions
-}
-```
-
-```java
-if (e instanceof ServerException) {
- try {
- org.json.JSONObject errorResponse = new org.json.JSONObject(((ServerException) e).responseBody);
- if(errorResponse.getInt("numCode")==9006){
- // Login expired
- }
- } catch (JSONException ex) {
- // ignore
- }
-}
-```
-
-```objc
-if (error) {
- if (error.code == 9006) {
- // log back in
- } else {
- // Other exceptions
-
-using TapTap.Support;
-
-TapSupport.Init("https://please-replace-with-your-customized.domain.com", "categorization ID", new TapSupportCallback
-{
- OnGetUnreadStatusError = (exception) => {
- if (e is TapException ex && ex.Code == 9006) {
- // Login expired, re-execute TDSUser.RetrieveShortToken and TapSupport.LoginWithTDSCredential
- } else {
- // Other exceptions
- }
- }
-});
-```
-
-```java
-import com.tds.tapsupport.TapSupport;
-import com.tds.tapsupport.TapSupportCallback;
-import com.tds.tapsupport.TapSupportConfig;
-
-TapSupportConfig config = new TapSupportConfig("https://please-replace-with-your-customized.domain.com", "categorization ID", new TapSupportCallback() {
- @Override
- public void onGetUnreadStatusError(Throwable e) {
- if (e instanceof ServerException) {
- try {
- org.json.JSONObject errorResponse = new org.json.JSONObject(((ServerException) e).responseBody);
- if(errorResponse.getInt("numCode")==9006){
- // Login expired, re-execute TDSUser.RetrieveShortToken and TapSupport.LoginWithTDSCredential
- } else {
- // Other exceptions
- }
- } catch (JSONException ex) {
- // ignore
- }
- }
- }
-});
-TapSupport.setConfig(this, config);
-```
-
-```objc
-- (void)onGetUnreadStatusError:(nonnull NSError *)error {
- if (error) {
- if (error.code == 9006) {
- // Login expired, re-execute TDSUser.RetrieveShortToken and TapSupport.LoginWithTDSCredential
- } else {
- // Other exceptions
- }
- }
-}
-```
-
-```cpp
-
-// Todo 2
-// Handle the EXPIRED_CREDENTIAL exception (9006) and retry the login process.
-// Prompt the developer to show other exceptions to the user
-
-TapUESupport::OnErrorCallBack.BindLambda([](const FTUError& Error)
-{
- if (Error.code == 9006)
- {
- /*** Login expired, re-execute User->RetrieveShortToken and TapUESupport::LoginWithTDSCredential */
- }
- else
- {
- /*** Other errors */
- }
-});
-
-```
-
-
-
-### Associated login for game's own account system
-
-If your game uses another user system, the customer service system also supports logging in with signed user information.
-
-:::caution requires a server
-Signing user information requires the use of a secret, so a server is required to use this method.
-:::
-
-The developer first needs to generate an auth secret in **Settings - Developer Settings - Player Authentication** in the Customer Service Workbench, and then **Use this secret on the server** to JWT-sign the player information using the HS256 algorithm. The player information (payload) should contain the user's unique identifier and a name for display, and should be structured as follows:
-
-```json
-{
- "sub": "U1234567", // unique identification
- "name": "Dash" // Displayed in the name of the work order, customer service back office
-}
-```
-
-
-JWT Example of a signature
-input:
-
-- Algorithm: HS256
-- payload: `{"sub": "U1234567", "name": "Dash"}`
-- secret: `44a23a3701955756301768bbb5dd1e1ea51500b556fb73201de76d5365150653`
-
-exports JWT:
-
-```
-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJVMTIzNDU2NyIsIm5hbWUiOiJEYXNoIn0.OV2LCP-7cLVTfjJlx21q9O1Tj_LlM0LFyW3OOu7CeNk
-```
-
-
-
-Finally, the obtained JWT is returned to the client, and the client calls the third-party account login interface of the SDK to complete the login:
-
-
-
-```cs
-TapSupport.LoginWithCustomCredential(jwt);
-```
-
-```java
-TapSupport.loginWithCustomCredential("User JWT");
-```
-
-```objc
-[TapSupport loginWithCustomeCredential:@"User JWT"];
-```
-
-```cpp
-// Todo 3
-TapUESupport::LoginWithCustomCredential(TEXT("User JWT"));
-```
-
-
-
-For security reasons, we usually add an `exp` field to the JWT payload to specify its expiration time, and the customer service system will honor the JWT's expiration time setting:
-
-```json
-{
- "sub": "U1234567",
- "name": "Dash",
- "exp": 1676546560 // Unix epoch
-}
-```
-
-Considering both security and performance, we recommend setting the JWT expiration time to 1-3 days from the current time.
-
-#### Exception Handling
-
-If an exception occurs in the SDK during login and subsequent processes, the developer can be notified via a callback.
-
-Among these exceptions, we need to handle the JWT expiration (`EXPIRED_CREDENTIAL`) exception in particular, and re-execute the above `request JWT, call login interface` process to log in the customer service again.
-
-For other kinds of exceptions, it is recommended to show them to the player directly. Since customer service exceptions usually do not affect the player's playing experience, we can consider only prompting the player that the customer service is unavailable at the customer service portal, and provide the interaction of manually triggering the retry.
-
-
-
-```cs
-
-if (e is TapException ex && ex.Code == 9006) {
- // Login expired
-} else {
- // Other exceptions
-}
-```
-
-```java
-if (e instanceof ServerException) {
- try {
- org.json.JSONObject errorResponse = new org.json.JSONObject(((ServerException) e).responseBody);
- if(errorResponse.getInt("numCode")==9006){
- // Login expired
- }
- } catch (JSONException ex) {
- // ignore
- }
-}
-```
-
-```objc
-if (error) {
- if (error.code == 9006) {
- // log back in
- } else {
- // Other exceptions
-
-using TapTap.Support;
-
-TapSupport.Init("https://please-replace-with-your-customized.domain.com", "categorization ID", new TapSupportCallback
-{
- OnGetUnreadStatusError = (exception) => {
- if (e is TapException ex && ex.Code == 9006) {
- // Login expires, refetches the new JWT and executes TapSupport.LoginWithCustomCredential(jwt)
- } else {
- // Other exceptions
- }
- }
-});
-```
-
-```java
-import com.tds.tapsupport.TapSupport;
-import com.tds.tapsupport.TapSupportCallback;
-import com.tds.tapsupport.TapSupportConfig;
-
-TapSupportConfig config = new TapSupportConfig("https://please-replace-with-your-customized.domain.com", "categorization ID", new TapSupportCallback() {
- @Override
- public void onGetUnreadStatusError(Throwable e) {
- if (e instanceof ServerException) {
- try {
- org.json.JSONObject errorResponse = new org.json.JSONObject(((ServerException) e).responseBody);
- if(errorResponse.getInt("numCode")==9006){
- // Login expires, re-fetches the new JWT and executes TapSupport.loginWithCustomCredential("new JWT");
- } else {
- // Other exceptions
- }
- } catch (JSONException ex) {
- // ignore
- }
- }
- }
-});
-TapSupport.setConfig(this, config);
-```
-
-```objc
-- (void)onGetUnreadStatusError:(nonnull NSError *)error {
- if (error) {
- if (error.code == 9006) {
- // The login expires, refetches the new JWT and executes [TapSupport loginWithCustomeCredential:@"new JWT"];.
- } else {
- // Other exceptions
- }
- }
-}
-```
-
-```cpp
-// Todo 2
-// Handle the EXPIRED_CREDENTIAL exception (9006) and retry the login process.
-// Prompt the developer to show other exceptions to the user
-TapUESupport::OnErrorCallBack.BindLambda([](const FTUError& Error)
-{
- if (Error.code == 9006)
- {
- /*** Login expires, re-fetches the new JWT and executes TapUESupport::LoginWithCustomCredential(TEXT("new JWT"));. */
- }
- else
- {
- /*** Other errors */
- }
-});
-```
-
-
-
-### Anonymous Login
-
-Anonymous login means that the game uses a string that only the current player can get as an anonymous identification (ID) to log into the customer service system.
-
-
-
-```cs
-TapSupport.LoginAnonymously("uuid");
-```
-
-```java
-TapSupport.loginAnonymously("uuid");
-```
-
-```objc
-[TapSupport loginAnonymously:@"uuid"];
-```
-
-```cpp
-TapUESupport::LoginAnonymously("uuid");
-```
-
-
-
-Anonymous login can be used to differentiate players by device in scenarios where the game does not have an account system or the player is not logged in, or to differentiate players by account after they have logged into their game account.
-
-#### Associating with Devices
-
-The game client generates and persists a UUID for the device, and then uses this ID to call the anonymous login interface to associate the player with the device.
-
-At this point, the anonymous ID on the device is the only identity credentials. If the ID is lost due to deletion of the application or clearing of local data, the player will not be able to access data such as historical work orders.
-
-#### associated with a game account
-
-Similarly, to be associated with a game account, the anonymous login interface needs to be called using an ID that has a one-to-one relationship with the account.
-
-The anonymous login mechanism is very flexible. For example, if you want a player to have a separate customer service account for each game, you can assign a separate anonymous ID to each game; if you want to track the player across games, you can use an anonymous ID at the pass level, in which case the customer service system doesn't know what the ID means, which is where the name "anonymous" comes from. This is where the name "anonymous" comes from.
-
-The price of flexibility is security. If not used correctly, anonymous login can lead to data leakage. The customer service system doesn't care where the anonymous ID came from, and can't do any verification, which means that the historical work order data of the player who leaked the ID will also be leaked. Therefore, **"only the current player can get it" needs to be guaranteed by the game side**. More specifically, the anonymous ID needs to meet the following conditions:
-
-- Unique and unchanging to the player
-- Cannot be guessed or deduced, cannot be enumerated.
-- It is not public, and will not be disclosed by the player through sharing or screenshots.
-
-
-Example of a correct anonymous ID
-
-- ✅ `b3a59993-c659-49f9-9f51-f2c808a472a0`:The game user system generates a UUID when a new player is created that serves as the player's anonymous ID for the customer service system, and the player logs in to log in to the customer service system with that ID.
-- ✅ `sha1('2436234209' + secret)`:Append a fixed secret to the player's UID on the server side and then hash it. Only after the player logs in can he get the ID to log into the customer service system.
-
-
-
-
-Example of an insecure anonymous ID
-
-Simply put, no client-only solution is secure:
-
-- ❌ `'2436234209'` Player UID: This is usually public information, visible and potentially shared by the player in the game, and a purely numeric ID that is easy to enumerate.
-- ❌ `sha1('2436234209')` :Hashing alone is still easy to enumerate when the algorithm is guessed.
-
-
-
-To reduce the risk of misuse, the anonymous login interface restricts the minimum length of the anonymous ID to 32.
-
-### Clear Login Status
-
-
-
-```cs
-TapSupport.Logout();
-```
-
-```java
-TapSupport.logout();
-```
-
-```objc
-[TapSupport logout];
-```
-
-```cpp
-TapUESupport::Logout();
-```
-
-
-
-## Open the customer service page
-
-
-
-```cs
-TapSupport.OpenSupportView();
-```
-
-```java
-TapSupport.openSupportView();
-```
-
-```objc
-[TapSupport openSupportView];
-```
-
-```cpp
-// TODO 4: Check if you can omit the parameter
-TapUESupport::OpenSupportView("/", nullptr, nullptr);
-TapUESupport::OpenSupportView();
-```
-
-
-
-:::info
-If the opened page has no content, you can first configure some subcategories for that game category in the Customer Service Workbench.
-:::
-
-### Scenario-based portals
-
-In addition to landing pages, the SDK also supports opening specific pages directly in specific scenarios.
-
-
-
-```cs
-TapSupport.OpenSupportView();
-```
-
-```java
-TapSupport.openSupportView("/path");
-```
-
-```objc
-[TapSupport openSupportViewWithPath:@"/path"];
-```
-
-```cpp
-// TODO 5: Check if you can omit the parameter
-TapUESupport::OpenSupportView("/path", nullptr, nullptr);
-TapUESupport::OpenSupportView(TEXT("/path"));
-```
-
-
-
-Different pages are distinguished by different path parameters. The currently supported pages are:
-
-| path | clarification |
-| ------------------------------- | ------------------------------------------ |
-| `/tickets/new?category_id={id}` | To submit a new work order, specify the id of the category. |
-| `/tickets` | Player work order list page to see all historical work orders of the player. |
-| `/articles/{id}` | Knowledge base article page. |
-
-### Reporting Information
-
-The work order module supports developers to collect additional information such as device, player, etc. through custom fields. Developers can bring in additional information when opening a support page (openSupportView), and the SDK will record this information in the work order fields when submitting a work order. This information can be viewed and analyzed in the customer service workbench.
-
-Before reporting data, you first need to define fields in the Customer Service Workbench (**Settings > Administration > Work Order Fields**). These fields need to be set to "Customer Service Only" access. Once created, you need to write down the ID of the field, which we will use in the SDK.
-
-
-
-
-
-```cs
-Dictionary fields = new Dictionary();
-fields.Add("243", "iOS 15.1"); // 243 is the ID of the "OS" field created in the background.
-fields.Add("244", "Dash"); // 244 is the ID of the Role Name field created in the backend
-
-TapSupport.OpenSupportView("/", fields);
-```
-
-```java
-Map fields = new HashMap<>();
-fields.put("243", "iOS 15.1"); // 243 is the ID of the "OS" field created in the background.
-fields.put("244", "Dash"); // 244 is the ID of the Role Name field created in the backend
-
-TapSupport.openSupportView("/", fields);
-```
-
-```objc
-NSDictionary *fields = @{@"243":@"iOS 15.1", @"244":@"WiFi"}; // 243 is the ID in the "OS" field created in the backend, 244 is the ID in the "Role Name" field
-[TapSupport openSupportViewWithPath:@"/" fieldsData:fields];
-```
-
-```cpp
-// TODO 6: Check if you can omit the parameter
-TSharedPtr Fields = MakeShareable(new FJsonObject);
-Fields->SetStringField("243", "iOS 15.1");
-Fields->SetStringField("244", "Dash");
-TapUESupport::OpenSupportView("/", Fields);
-```
-
-
-
-In addition to setting it when opening the customer service page, developers can also set the global default field information through the following interface:
-
-
-
-```cs
-TapSupport.SetDefaultFieldsData(fields: fields);
-```
-
-```java
-TapSupport.setDefaultFieldsData(fields);
-```
-
-```objc
-[TapSupport shareInstance].defaultFieldsData = fields;
-```
-
-```cpp
-TapUESupport::SetDefaultFieldsData(Fields);
-```
-
-
-
-Global fields can be updated after setup:
-
-
-
-```cs
-TapSupport.DefaultFields = new Dictionary {
- { "key", "value" }
-};
-```
-
-```java
-TapSupport.updateDefaultField("key", "value");
-```
-
-```objc
-[TapSupport updateDefaultFieldWithValue:@"value" forKey:@"key"];
-```
-
-```cpp
-// TODO 7
-TSharedPtr Value = MakeShared(TEXT("Value"));
-TapUESupport::UpdateDefaultField(TEXT("key"), Value);
-```
-
-
-
-Globally set fields are merged with the incoming `fields` parameter at OpenSupportView time. Fields passed in during the `OpenSupportView` method have a higher priority than global fields, meaning that global fields will not take effect if they are the same.
-
-### Closing the support page
-
-Players can click the close button within the customer service page to exit. However, in certain scenarios, the game may need to actively close the customer service page:
-
-
-
-```cs
-TapSupport.CloseSupportView();
-```
-
-```java
-TapSupport.closeSupportView();
-```
-
-```objc
-[TapSupport closeSupportView];
-```
-
-```cpp
-TapUESupport::CloseSupportView();
-```
-
-
-
-## Unread Message Notification
-
-Unread messages are generated when there are new developments in a submitted work order (e.g. a new customer service response). Usually in-game, players are notified of unread messages in the customer service portal using red dots, etc. The SDK automatically polls for dimensional messages, and when the status of an unread message changes - from none to yes or yes to no - the SDK notifies the developer via a callback to notify the developer:
-
-
-
-```cs
-using TapTap.Support;
-
-TapSupport.Init("https://please-replace-with-your-customized.domain.com", "categorization ID", new TapSupportCallback
-{
- UnReadStatusChanged = (hasUnRead, exception) =>
- {
- Debug.Log($"hasUnRead:{hasUnRead} exception:{exception}");
- }
-});
-```
-
-```java
-import com.tds.tapsupport.TapSupport;
-import com.tds.tapsupport.TapSupportCallback;
-import com.tds.tapsupport.TapSupportConfig;
-
-TapSupportConfig config = new TapSupportConfig("https://please-replace-with-your-customized.domain.com", "categorization ID", new TapSupportCallback() {
- @Override
- public void onUnreadStatusChanged(boolean hasUnread) {}
-});
-TapSupport.setConfig(this, config);
-```
-
-```objc
-#import
-
-// callback need to be realized TapSupportDelegate
-TapSupportConfig *config = [TapSupportConfig new];
-config.server = @"https://please-replace-with-your-customized.domain.com";
-config.productID = @"categorization ID";
-config.callback = self;
-[TapSupport shareInstance].config = config;
-```
-
-```cpp
-TapUESupport::OnUnreadStatusChanged.BindUObject(this, &YourUObject::OnUnreadStatusChanged);
-```
-
-
-
-:::info
-Developers do not need to additionally clear the local unread notification status (small red dot) when tapping the customer service portal. This is because clicking on the customer service portal does not mean that the player has viewed all unread work orders. If the player has viewed all unread work orders, the SDK will get the latest status and notify the developer via the callback mentioned above.
-:::
-
-### Pause Polling
-
-The SDK's built-in polling mechanism intelligently adjusts the frequency of getting the status of unread messages. However, there are some scenarios where these requests are still unnecessary overhead, such as when a player wants to pause all unnecessary background requests during a game match (when there is no red dot displayed on the interface), and the SDK provides a pair of APIs for controlling polling for this purpose:
-
-- `Pause`: pauses polling
-- `Resume`: resumes polling.
-
-
-
-```cs
-TapSupport.Resume();
-TapSupport.Pause();
-```
-
-```java
-TapSupport.resume();
-TapSupport.pause();
-```
-
-```objc
-[TapSupport resume];
-[TapSupport pause];
-```
-
-```cpp
-TapUESupport::Resume();
-TapUESupport::Pause();
-```
-
-
-
-
-SDK Polling Policy
-
-- Initially, a request is initiated immediately, and the next request interval is set to be 10s.
- - If there is no change in the status of an unread message compared to the current status, increase the interval by 10s until the maximum interval is 300s, and reset the interval to 10s if there is a change.
- - If the player never opens the WebView, the interval is increased to a constant 3600s.
-- Calling the `Resume` method resets the polling to its initial state.
-
-
-
-
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/_category_.json
deleted file mode 100644
index 6cff07e1a..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/_category_.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "label": "TapDB",
- "collapsed": true,
- "position": 3
-
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/changelog/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/changelog/_category_.json
deleted file mode 100644
index d1f31d6b5..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/changelog/_category_.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "label": "Changelog",
- "position": 8
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/changelog/android.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/changelog/android.mdx
deleted file mode 100644
index cae8da922..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/changelog/android.mdx
+++ /dev/null
@@ -1,25 +0,0 @@
----
-title: Android
-sidebar_position: 3
----
-
-## 2.2.0 | 2022-07-14
-
-### What's new
-
-* Adjust the interface display of frozen items.
-
-## 2.1.0 | 2022-04-30
-
-### What's new
-
-* New multi-account switching function, developers can add multiple domestic or overseas accounts and switch between them.
-
-## 2.0.0 | 2022-01-13
-
-### What's new
-
-* New kanban function: you can view the kanban posted on the web side through the client.
-* Fixed the data display problem of the advertising module.
-
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/changelog/ios.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/changelog/ios.mdx
deleted file mode 100644
index b3cd85abd..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/changelog/ios.mdx
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: iOS
-sidebar_position: 2
----
-
-## 2.2.0 | 2022-07-18
-
-### What's new
-
-1. Optimize iPad landscape experience.
-2. Adjust the interface display of frozen items.
-3. Fix the problem of inaccurate display of weekly active and monthly active dates.
-
-## 2.1.0 | 2022-04-30
-
-### What's new
-
-* New multi-account switching function, developers can add multiple domestic or overseas accounts and switch between them.
-
-## 2.0.0 | 2022-01-13
-
-### What's new
-
-* New kanban function: you can view the kanban posted on the web side through the client.
-* Fixed the data display problem of the advertising module.
-
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/changelog/web.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/changelog/web.mdx
deleted file mode 100644
index dcdff026b..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/changelog/web.mdx
+++ /dev/null
@@ -1,90 +0,0 @@
----
-title: Web
-sidebar_position: 1
----
-
-## 2.17.0 | 2024-07-01
-
-### What's new
-
-1. Dimension Renaming in Analysis Module: Dimensions can now be renamed within the analysis module.
-2. New Data Alert Feature: Introduced a data alert feature that can send notifications to Enterprise WeChat, Slack, and email upon triggering an alert.
-3. Display Game Status on TapTap: Shows specific statuses of games on TapTap, such as Test Server or Early Access, to help distinguish between projects with the same name.
-4. Custom Callback Configuration for TapTap Ad Channels: TapTap ad channels now support custom callback configuration.
-5. Ad Callback Log Query: Supports querying ad callback logs across all channels.
-6. 31-60 Day Retention: Added retention metrics for 31-60 days.
-7. Filter for Active Projects on Game List Page: The game list page now supports filtering for active projects.
-8. Project Hiding: Supports hiding projects, which will no longer appear in the game list once hidden.
-9. Shared Filter Conditions: Filter conditions can now be shared with project team members.
-10. Refund Data in Payments: Added refund data to the payment section.
-11. User Value (LTV): Supports displaying the daily LTV multipliers compared to the first day's LTV.
-12. Global Observer Role: Introduced a global observer role with permissions for all projects under the enterprise (including new projects), but without management rights.
-13. Weekly and Monthly Promotion Costs in Overview Revenue Module: Added weekly and monthly promotion costs to the revenue module in the overview.
-
-## 2.15.2 | 2022-09-08
-
-### What's new
-
-* Diagnosis: Crash analysis and error analysis problem detail page, added open new window button.
-
-
-## 2.15.1 | 2022-09-01
-
-### What's new
-
-* Crash analysis and error analysis can share filter criteria by link
-
-
-## 2.15.0 | 2022-08-26
-
-### What's new
-
-1. New diagnostic module: including crash analysis, error analysis, symbol table, label management 4 functions.
-2. Access to TapSDK 3.14.0 or later is required to use the Diagnostic Module.
-3. In order to enhance the analysis capability, we provide some fields related to cell phone hardware for all projects, which can be used in the "Analysis" module.
-
-| Field Name | Display Name |
-| --- | --- |
-| product_name | product name |
-| release_year | release year |
-| chipset_brand | chipset brand |
-| chipset_model | chipset model |
-| single_core_score | single core score |
-| multi_core_score | multi core score|
-| price_1_median 1 | Current market price |
-| price_2_median 2 | Current Used Price |
-
-
-## 2.14.3 | 2022-08-11
-
-### What's new
-
-* Optimized the operation experience of "Configuration Module - Early Warning Management".
-
-
-## 2.14.2 | 2022-07-28
-
-### What's new
-
-* Optimized the operating experience of most functions of the configuration module
-
-## 2.14.1 | 2022-07-14
-
-### What's new
-
-* Optimized the operation experience of "Configuration - User Tab".
-
-## 2.14.0 | 2022-06-29
-
-### What's new
-
-1. Event analysis no longer requires pre-selection of analysis subjects, and now supports selecting different analysis subjects for multiple metrics at the same time.
-2. Simplified table names in SQL analysis.
-3. The descriptions of table fields are added in SQL analysis, so that you can check them synchronously when using.
-
-## 2.13.0 | 2022-06-09
-
-### What's new
-
-* When switching the language to English, the menu now supports English display.
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/faq/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/faq/_category_.json
deleted file mode 100644
index 60548d80c..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/faq/_category_.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "label": "FAQ",
- "position": 6
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/faq/ads.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/faq/ads.mdx
deleted file mode 100644
index e7ccef063..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/faq/ads.mdx
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: Advertising Questions
-sidebar_position: 3
----
-
-### Q: How to create an advertising plan?
-
-A:Enter TapDB's ad page, click Ad Management, select New Ad Campaign in the upper right corner, select the ad platform, pay attention to the matching method, fill in the information, and generate the placement link.
-
-### Q: What is IP Matching?
-
-A:"IP Matching" is used to check if the IP of the user when clicking on the ad is the same as the IP when opening the game, and is applicable to all ads. Custom ads can be created if no corresponding platform is found when creating a campaign.
-
-### Q: What is Device Matching?
-
-A:Device Matching is that TapDB and the ad platform have finished interfacing, and the ad platform will return the data of clicked ads to TapDB to match the new users by device information, which is very accurate, but it is only used in the ad platform that has finished interfacing with TapDB.
-
-### Q: How to place IP matches and device matches?
-
-IP Matching: When you add an ad, select the ad platform, the IP matching ad platform is marked, and the IP matching ad needs to fill in the download link, after generating the link, directly fill in the IP matching generated placement link into the landing page URL of the ad platform.
-
-Device Matching: The callback link can be generated directly after filling in the corresponding parameters of Device Matching. The callback link can be filled in the third-party monitoring link (the name may be different) of the ad platform, and the URL of the landing page can be filled in the download address of the game itself.
-
-### Q: What if I can't use TapDB links to place ads?
-
-A:For example, if the domain name of the game is a.b.c, you need to resolve a.b.c to [l.tapdb.net](http://l.tapdb.net) in the form of CNAME, then if the connection given by TapDB is `https://l.tapdb.net/xxxx`, directly replace [l.tapdb.net](http://l.tapdb.net) with a.b.c, replace "https" with "http", and finally become `http://a.b.c/xxxx`, and use this link to deliver the advertisement.
-
-### Q: Why does the ad match players from other systems?
-
-A:There are possible reasons why ads are matched to players on other systems.
-
-(1) The placement is on the web, and both iOS and Android users will be matched.
-
-(2) If the link is placed on iOS, Android users (who can see the ad) may click on it and actively search for the app to download, and then they will be matched.
-
-### Q: How do I enable data callbacks for the TapTap ad channel?
-A: Go to Ads -> Ad Management -> Channel Settings. You can enable data callbacks for the TapTap ad channel there. Currently, it supports callbacks for activation, registration, payment, and next-day retention data.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/faq/tran.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/faq/tran.mdx
deleted file mode 100644
index 4bc96881f..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/faq/tran.mdx
+++ /dev/null
@@ -1,103 +0,0 @@
----
-title: SDK Integration Questions
-sidebar_position: 1
----
-
-### Q: SDK initialization was successful, why no new data?
-
-A: Before you fix the problem, you should have a rough understanding of the data reporting process:
-
-1. The game App calls the relevant interfaces of the SDK to report data.
-2. DB platform receives the reported data and displays it in the "Reporting Details".
-
-The troubleshooting method is also based on this process step by step. Open Configuration - Report Details and check:
-
-1. Check if initialization is the earliest call? If not, adjust and try again;
-2. Observe whether the reported data is displayed in the "Reporting details" function; If the data is displayed, the data is reported normally. If no data is displayed, enter 3;
-3. Open the mobile bag capture tool to test whether the equipment reports data normally.
-
-### Q: The SDK initialization was successful, why is the added data incorrect?
-
-A: You can use the "Report Details" function to verify the accuracy of buried point reporting.
-
-### Q: How can I check the reported data?
-
-A: You can check the reported data through "Report Details" and "Buried Site Management.
-
-"Reporting Details": you can use "Reporting Details" to view the real-time reporting of buried data during the debugging stage of SDK access and buried testing.
-
-"Buried point management" : you can view the data reception of the project in the last 7 days, quickly understand the overall situation of buried point reporting, as well as the details of error reporting and sampling examples.
-
-### Q: Why did the SDK initialization fail?
-
-You can try to debug the gameversion field, if there is no value, you need to fill in the value.
-
-### Q: How to store userId of single player game?
-
-A: The userId of the standalone game needs to pay attention to the following points.
-
-(1) iOS has to generate an ID by itself and store it in the certificate space. iOS has a storage space, one for application and one for certificate (enterprise).
-
-(2) Android try to save to SD card, use a user operation or call `setUser` after 1 minute.
-
-(3) Randomly generate a unique user ID and save it locally.
-
-### Q: Why no new data is added after SDK integration?
-
-A: Please check whether the initialization of the SDK is called successfully, in addition the initialization should be called at the earliest. If the initialization fails, then do the processing according to the failure log.
-
-### Q: Do both the server and client need to pass the recharge data?
-
-A: You can choose one way to pass the recharge data in the server side and the client side. If you pass both, the recharge data will be doubled.
-
-### Q: Why the TapDB page does not show the revenue or the revenue is more than the actual revenue when the server side delivers the recharge data?
-
-A: First of all, only one of the server-side and client-side interfaces can be used (if both are used, it will show double the recharge amount and make sure the recharge is successful before sending the recharge data), and secondly, the user_id in "identify": "user_id" on the server side and the "userId" in setUser in the document "Record a Player" need to be consistent.
-
-### Q: Why does TapDB show no data after sending real-time online data?
-
-A: There may be several reasons for not displaying data.
-
-(1) Check the file format, parameter type (returning 400 error means the format is not correct).
-
-(2) Pay attention to the timestamp unit (seconds), and only send the last 7 days of data, too early data will not be saved.
-
-(3) Note: **whether there is a required header information: `Content-Type: application/json`.**
-
-### Q: Can I not fill in the parameters that can be empty?
-
-A: No, the parameters that can be empty are filled with `null`.
-
-### Q: Why can't I find the subcontracted channel in TapDB after filling in the channel?
-
-A: There must be an added data for TapDB to receive the channel and display the subcontracted channel in this page.
-
-### Q: When TapDB reports events, are the letters of the events case-sensitive?
-
-A: All keys are case-insensitive, and all values are case-sensitive. The event name is value, the property name is key, and the property value is value.
-
-### Q: When reporting a custom event, must the property type be exactly the same as the registered one? Is there any compatibility policy?
-
-A: It must be identical, there is no compatibility policy. The wrong type will be discarded by the agent as dirty data.
-
-### Q: Is it possible to deploy privately?
-
-A: Private deployment is not supported for now. But we will ensure the security of your data.
-
-### Q: How to set the permission?
-
-A: You can modify account permissions in "Enterprise Settings" - "Permission Management" - "Edit Members".
-
-### Q:After reporting the Custom Event, no data can be found in the backend.
-
-A: Please first check if the [user_id](/sdk/tapdb/sdk/client-side-integration/#设置账号-id) is set in the code. If you're unsure, you can go to the backend and use SQL queries to see the user_id that has been reported previously.
-
-
-![ SQL Link](/img/数据分析-Sql查询.png)
-
-```SQL
-SELECT * FROM hive_saas1.tapdb."users"
-WHERE user_id LIKE'%dTJTp6sA+OWsZ7Jf0JmGg==%'
-LIMIT 100
-```
-Check if the user_id that needs to be reported is present in the reported user_id.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/faq/user.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/faq/user.mdx
deleted file mode 100644
index c8ba306ae..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/faq/user.mdx
+++ /dev/null
@@ -1,36 +0,0 @@
----
-title: SDK Usage Questions
-sidebar_position: 2
----
-
-### Q: How can I view the data under various dimensions?
-
-A:The table in the four pages of source, online, retention and payment has the option of switching the main dimension. The data displayed in each dimension is different, and the data under each dimension can be viewed according to different situations.
-
-### Q: What is subcontracting channel?
-
-A: The subcontracting channel is the information in the channel field when the data is reported, which is convenient for you to split the data. See [TapSDK initialization](/sdk/tapdb/sdk/client-side-integration#tapsdk-init) for details on how to connect.
-
-### Q: How to use filter conditions?
-
-A:Filter conditions can be selected according to different options, different ranges of TapDB table presentation data, easy to distinguish between various types of users. You can add multiple filter criteria to filter the data that meets the criteria precisely, or you can save the filter criteria for future use.
-
-### Q: Why is the source income less than the actual income?
-
-A:The source only shows the payment income of new users, and the payment information of old users can be found in the payment page.
-
-### Q: Why is the conversion rate so low?
-
-A:There are several possible reasons for low conversion rates:
-
-(1)The technician makes sure that the SDK calls' setUser 'when the user logs in.
-
-(2) Operators should pay attention to whether there is a large number of new equipment, there may be cheating machines in the refresh increase.
-
-### Q: How can I check what version of TapDB SDK is connected to my project?
-
-A:In event analysis, use "SDK version at the time of event" as the dimension and "any event" as the indicator to query.
-
-### Q: How to verify the accuracy of buried data?
-
-A:Use buried point management to verify the accuracy of buried points.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features.mdx
deleted file mode 100644
index f6d8295d3..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features.mdx
+++ /dev/null
@@ -1,53 +0,0 @@
----
-title: TapDB Introduction
-sidebar_label: Introduction
-sidebar_position: 1
----
-
-TapDB is a set of analysis tools focused on solving the data needs of game projects. It is committed to helping developers achieve low-cost and efficient access and query experience.
-
-## Original Intention
-
-As game developers, we know that making games is not easy. Thinking about how to polish game products can be a headache, and choosing the right set of data tools and using them correctly can be a lot of work and ineffective. We believe there must be high value in providing a low-barrier, easy-to-use, and accurate tool.
-
-Over the years of making games, we have accumulated some experiences and methods, and we hope to share them with you through TapDB. We believe that helping developers create quality games is helping TapTap grow.
-
-## Function
-
-The main functions provided by TapDB service are:
-
-### Basic BI
-
-The four data reports are very practical and have a low learning threshold. That's what we've learned from decades of gaming.
-
-### Derived Event
-
-For the developer's experience, we have reduced the query time of the base BI to 1/10th of the time through a series of derived events, ensuring that you can quickly access the data you need.
-
-### Tracking Advertising Delivery
-
-Embed into the world's leading AD delivery systems (such as Ocean Engine, AMS, AppsFlyer, etc.) for easy AD tracking, callbacks, and data analysis.
-
-### Permission Role
-
-Each user is given a separate account and individually controlled permissions to ensure data security.
-
-### Custom Event Analysis
-
-- Freedom to customize events and focus on the behaviors that matter most.
-
-- Multi-dimensional perspective, quickly disassemble data, more efficiently find the root cause of the problem.
-
-- Log-level query capability to know exactly what the user has done.
-
-## Advantages And Features
-
-- Low-threshold access: Access to basic events is very simple, which is enough to give you perfect analytics and ad placement capabilities.
-
-- No delay: data can be checked immediately after reporting, time is life.
-
-- Keep updated game analysis framework: We will share the latest experience and methods with you continuously.
-
-- Combined with the data of TapTap ecology, we provide developers with full-chain game data analysis capability.
-
-- Free.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/_category_.json
deleted file mode 100644
index 875bf03cc..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/_category_.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "label": "功能指南",
- "position": 4
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/_category_.json
deleted file mode 100644
index 70251bade..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/_category_.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "label": "Custom Event",
- "position": 1
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/ad.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/ad.mdx
deleted file mode 100644
index 5c4b2d40a..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/ad.mdx
+++ /dev/null
@@ -1,78 +0,0 @@
----
-title: Ad Management
-sidebar_position: 16
----
-
-## 1. Overview
-
-The number of new users per day is a very popular metric in Internet products. There are many more in-depth analyses that can be done on this metric of new users, such as
-
-- Analyzing the number of new users brought by different channels in order to optimize the placement strategy.
-- Analyzing the retention and conversion of new users brought by different channels, so as to compare the quality of users brought by different channels.
-- Analyzing the percentage of daily revenue contributed by different channels.
-
-On the Ads Overview page, you can see the overview data under all ads. You can also see the placement data for each ad link under each ad platform.
-
-![](/img/customEvent/ad/广告概览new.png)
-
-## 2. Ad Management
-
-In this page you can: set up ads, cost management, attribution settings, and view deleted ads.
-
-### 2.1 Ad Settings
-
-You can add, edit, query and delete on advertising campaigns. As shown in the figure.
-
-![](/img/customEvent/ad/广告管理.png)
-
-A: Add an ad
-After clicking it you can create an ad, enter the ad name, select the ad platform to be placed, select the ad tag, and select the sub-dimension.
-Click "Generate Placement Link" and the system will automatically generate the placement link.
-
-![](/img/customEvent/ad/新增广告.png)
-
-B: Tag Managemen t
-Ad tags are notes and markers for each campaign, allowing you to quickly filter out campaigns that have certain characteristics. In tag management, you can view existing tags and tagged campaigns, create new tags, and delete unwanted tags.
-
-![](/img/customEvent/ad/标签管理.png)
-
-C: Search Ads
-Enter a search term and you can search with the name of the ad campaign.
-
-D: Create similar ads
-When you need to create multiple similar ads for monitoring at one time, you can click Create Similar Ads to mark each ad campaign.
-
-E: Edit Ads
-Make edits to the ad campaign.
-
-F: Delete Ads
-Deleted campaigns will be displayed on this page and you can restore the campaign. Ads deleted for more than 7 days will be automatically cleared.
-
-### 2.2 Cost Management
-
-On the Cost Management page you can see the daily cost of each campaign, and the cost-related metrics on the Ads Overview page will be calculated based on the data here. The prerequisite is that you have to enter the costs into the system first. You can enter the costs directly in the cost management screen, or you can click "Import Costs" to import costs in bulk as a template.
-
-![](/img/customEvent/ad/成本管理.png)
-
-![](/img/customEvent/ad/上传成本.png)
-
-### 2.3 Attribution Setting
-
-You can set the attribution "default window", and you can also set a separate attribution window for each platform. When no separate attribution window is set for the ad platform, the attribution window will follow the system window.
-
-![](/img/customEvent/ad/归因设置.png)
-
-### 2.4 Deleted Ads
-
-Check the deleted ad campaign and click "Restore" to restore Ads.
-
-![](/img/customEvent/ad/恢复广告.png)
-
-### 2.5 Tools - Create Link
-
-In "Tools" - "Create Link", you can share ad data to others. You can choose to share to ordinary external members or advertising agencies:
-
-1. share to ordinary external members: external members can only query the ad data.
-2. share to ad agencies: agencies can query ad data and also support in creating and modifying ads and managing ad costs.
-
-![](/img/customEvent/ad/创建链接.png)
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/alert.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/alert.mdx
deleted file mode 100644
index 5aa227f9d..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/alert.mdx
+++ /dev/null
@@ -1,172 +0,0 @@
----
-title: Alert Management
-sidebar_position: 18
----
-
-## 1. Overview
-
-Construct a set of time series data by setting indicators and filtering grouping items. Timely query and compare with historical data according to time granularity, and notify project team members after triggering alert rules.
-
-Currently, we support setting indicators in the form of "event analysis" and alert notification by email.
-
-![概述](/img/customEvent/alert_1.png)
-
-## 2. Applicable Roles and Uses
-
-| Roles | Uses |
-| ---------- | ------------------------------------- |
-| Analyst / Business Person | To monitor key or abnormal data for early warning, grasp product dynamics in real time, and can find abnormal problems at the first time |
-
-## 3. New Data Alert
-
-Click New Alert in the upper right corner of the alert list.
-
-![新建数据预警](/img/customEvent/alert_2.png)
-
-### 3.1 Basic Information
-
-In the "Basic Information" section, enter or select the warning name, query object and warning indicator in turn.
-
-![填写基础信息](/img/customEvent/alert_3.png)
-
-The alert name is displayed in the alert list, which is the basis for identifying an alert.
-
-The query subject can choose "Account" or "Device", similar to the query subject in "Event Analysis".
-
-### 3.2 Early warning rules
-
-#### 3.2.1 Adding alert rules
-
-In the warning rule, you can select the grouping dimension and construct multiple groups of time series data at the same time by multi-selecting the grouping value. When there is no grouping dimension, the default grouping item is "Total".
-
-![预警规则](/img/customEvent/alert_4.png)
-
-The relationship between the optional alert time granularity, comparison base and parameter types are listed in the following table.
-
-
-
-
Time granularity
-
Comparison Basis
-
Parameter Type
-
-
-
Day
-
Fixed value
-
Value
-
-
-
Previous day, same day as last week
-
Value, percentage
-
-
-
Past 7-day average, past 30-day average
-
Value, percentage, standard deviation
-
-
-
Hour
-
Fixed values
-
Value
-
-
-
Previous hour, same hour yesterday
-
Value, percentage
-
-
-
Past 24 hour mean
-
Value, percentage, standard deviation
-
-
-
-每个预警下可同时设置多条预警规则,每个预警规则下可添加多个分组。
-
-#### 3.2.2 Alert Rule Details
-
-**Value**
-
-When fixed value is used as the comparison base:
-
-* High: actual value of indicator > Numerical value
-
-* Low: actual value of indicator < value
-
-When non-fixed values are used as the comparison benchmark:
-
-* High: actual value of the indicator > the benchmark + the value
-
-* Low: actual value of the indicator < the benchmark - the value
-
-
-**Percentage**
-
-* High: actual value of indicator > benchmark * (1 + percentage)
-
-* Low: actual value of indicator < benchmark * (1 - percentage)
-
-**standard deviation**
-
-* High: actual value of indicator > comparative benchmark + parameter * standard deviation
-
-* Low: actual value of indicator < comparative benchmark - parameter * standard deviation
-
-
-For example, the standard deviation of the "average value for the past 7 days" is the standard deviation of the actual value of the indicator for each day of the past 7 days.
-
-### 3.3 Notification settings
-
-Currently, we support email notification, and we will open various notification methods such as webhook, SMS and in-site notification.
-
-![通知设置](/img/customEvent/alert_5.png)
-
-Enter user email and press enter to confirm the entry, multiple emails can be entered.
-
-## 4. Warning Notification
-
-According to the time granularity set for each warning rule, the system will query the warning indicator at the end of each day or hour according to the set grouping.
-
-An email will be sent to the entered mailbox after the alert rule is triggered, as follows.
-
-![预警通知](/img/customEvent/alert_6.png)
-
-## 5. Alert Management
-### 5.1 Alert List Management
-
-The created alerts are displayed as a list in the data alert page, and you can view, start/pause, edit, copy, delete, etc.
-
-![预警列表管理](/img/customEvent/alert_7.png)
-
-The progress of using data alert is based on "alert instance" as the most basic unit. One grouping under the alert rule is one "alert instance", the maximum number is 30, and the usage progress can be released by deleting the alert or unchecking the grouping.
-
-### 5.2 Alert details
-
-Click the alert name and alert rule to jump to the detail page, and filter the alert rule.
-
-![预警详情_1](/img/customEvent/alert_8.png)
-
-On the left side of the page, you can see the "Basic Information", "Alert Rules" and "Notification Settings" of the alert.
-
-On the right side of the page, you can see the historical trend graph and data table of the current filtered alert rules and grouping data, and the time when an alert occurred is marked in the trend graph as a red dot.
-
-![预警详情_2](/img/customEvent/alert_9.png)
-
-## 6. Best Practice
-
-### 6.1 Monitoring performance targets by "fixed value" alert
-
-For numerical performance targets, such as the amount of recharge and the number of active users, you can monitor whether the performance targets are achieved through "fixed value" alerts.
-
-### 6.2 Through the "percentage" monitoring indicators month-on-month and year-on-year changes
-
-For daily operation indicators, such as number of active users and number of new users, you can closely monitor their trends through "percentage" alerts, so that you can pay attention to them in time if there are abnormal trends.
-
-
-### 6.3 Monitor short-term abnormal changes with long-term trend through "Standard deviation" alert
-
-For indicators with long-term growth or decay, "standard deviation" alert can eliminate the influence of long-term trends on short-term changes, so as to monitor the short-term abnormal changes of the indicator.
-
-### 6.4 Monitoring alert intervals
-
-For some important indicators, such as the number of active users, multiple alert rules can be created to monitor whether the performance target is achieved, the trend of change, etc. Two rules can be created for the same kind of monitoring rules, "high" and "low", to monitor the change interval of indicators.
-
-### 6.5 Multi-group alerting to detect abnormal data dimensions
-
-For some indicators that have changed abnormally, you can find the main dimensions that caused the change by splitting them into groups for further drill-down analysis.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/cluster.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/cluster.mdx
deleted file mode 100644
index ef576ee72..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/cluster.mdx
+++ /dev/null
@@ -1,113 +0,0 @@
----
-title: User Clustering
-sidebar_position: 9
----
-
-## 1. Overview
-
-User Clustering is a way to divide users who meet certain properties and behavioral characteristics into a group, and to study and analyze the group.
-
-In the analysis model of TapDB, "account" and "device" are used as query subjects, and "account" and "device" are also supported as subjects in user clustering.
-
-Currently, we support 3 types of user clustering: "Conditional Clustering", "ID Clustering" and "Result Clustering".
-
-![概述](/img/customEvent/cluster_summary.png)
-
-## 2. Applicable Scene
-
-
-| Roles | Usage |
-| ---------- | ---------------------------- |
-| Analyst/business person | Segmenting specific user groups for focused analysis, troubleshooting, or exporting user lists, etc. |
-
-## 3. Creating User Clustering
-
-### 3.1 Conditional Clustering
-
-Select "New sub-group" and "New Condition group" in the upper right corner of the subgroup page. Please pay attention to the progress here.
-
-This page is divided into two parts: "basic information" and "Group rules".
-
-![新建条件分群](/img/customEvent/cluster_create_condition_cluster1.png)
-
-On the "basic information" page, enter or select "Group display name, Group, Group name, Update method, and Remark" in order.
-
-Group display name: displayed in the group list and analysis page, which is the basis for business personnel to identify subgroups.
-
-Group: support "account" or "device", choose according to the business scenario.
-
-Group name: It is the unique identification of the subgroup stored in the system background, and can be named as the parameter name with business meaning for the convenience of data analysts to query the database table directly.
-
-The update method is divided into "Manually" and "cycle". "Manually" means that the system will not automatically update the user group after the first calculation is completed, and users need to update manually; "cycle" means that the system will update the user group after 0:00 every day, taking the previous day as the base, and you can set the update delay to ensure that all the data of the previous day are received to ensure data integrity.
-
-![新建条件分群](/img/customEvent/cluster_create_condition_cluster2.png)
-
-In the "Group Rules" page, the rules are divided into two parts: "User attribute satisfaction" and "User behavior satisfaction", and you can switch the "and or" relationship between the two parts.
-
-In the "User behavior satisfaction" section, the attribute conditions are set based on the selected subgroup subject, and each attribute condition can switch the "and or" relationship.
-
-In the "User behavior satisfaction" section, it can be divided into two categories: "Never done event" and "done event", and both conditions can be added multiple times.
-
-
-"Never done event" means that the user has not done the behavior in the selected time period.
-
-"Done event" means that the user has done the behavior in the selected event period, and the results of the behavior can be filtered.
-
-### 3.2 ID Clustering
-
-Select "New sub-group" and "Upload ID group" in the upper right corner of the subgroup page.
-
-![新建 ID 分群](/img/customEvent/cluster_create_id_cluster.png)
-
-Upload the ID file on the current page, and the system will correlate the IDs in the file with the existing user data in the system according to the selected "group subject" to find the eligible users.
-
-File format requirements: one ID per line, CSV file encoded in UTF-8 format. If there is an unmatched item in the upload (i.e. a user with this ID does not exist in the project), the item will be skipped and will not be included in the group. The template can be downloaded as a reference.
-
-### 3.3 Result Clustering
-
-In each analysis model, if the indicator is the number of users (e.g. "number of triggered users" in event analysis, retained or churned users in a segment of retention or funnel analysis), you can create groups by clicking "New result groups" in the result report.
-
-![新建结果分群](/img/customEvent/cluster_create_result_cluster1.png)
-
-You can set the name and remark of the resulting group.
-
-![新建结果分群](/img/customEvent/cluster_create_result_cluster2.png)
-
-You can't modify and update the rules of resulting group, you can only modify the name and remark.
-
-## 4. Various Operations
-
-The created clusters are displayed as a list on the user clustering page.
-
-![对用户分群的各类操作](/img/customEvent/cluster_operation.png)
-
-Users can view, edit, delete, update, download, and copy operations on the cluster, as follows.
-
-| Operation | Location | Effect |
-| --- | --- | ---------------- |
-| View | Name | View cluster Details |
-| Edit | Action bar | Enter the Edit cluster popup |
-| Delete | Action Bar | Delete cluster |
-| Update | Action bar | Manually start the cluster calculation and update the cluster results |
-| Download | Action bar | Download the list of users with the current cluster results|
-| Copy | Action bar | Create a new cluster with the same parameters as the current one |
-
-## 5. Using User Clustering
-
-### 5.1 Focusing or excluding some users
-
-Focusing on high-value users who meet certain criteria, such as: those who pay more than $100, so as to target strong paying users in the analysis model and understand their behavioral characteristics.
-
-Excluding suspicious users that meet specific conditions, such as: devices that have been active on the same device for more than 3 accounts, you can exclude such studio devices without affecting the analysis results of normal users.
-
-### 5.2 User data import and export
-
-Import external user data into the system to create cluster, such as: the company already has a group of users and devices with high paying ability in other game projects, so it can be imported to explore their active and paying situation in this project and better guide potential high paying users to pay.
-
-Download the cluster results as the basis of operation in other systems, such as: exporting suspected studio devices, accounts, and then punishing and banning them in the game operation system.
-
-### 5.3 The analysis results of the drill-down analysis
-
-The result cluster is based on the report of the analytic model and is therefore well suited as a basis for drill-down analysis.
-
-For example, the funnel analysis calculates the funnel conversion of users browsing products, initiating orders, and paying for orders, and finds that a large number of users are lost after initiating orders. In this case, the result cluster of the lost users can be analyzed to explore the reasons why they initiated orders but did not pay successfully by analyzing the attributes and behaviors.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/data-model.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/data-model.mdx
deleted file mode 100644
index 162b04165..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/data-model.mdx
+++ /dev/null
@@ -1,114 +0,0 @@
----
-title: Burial Point Design Guide
-sidebar_position: 2
----
-
-## 1. Case Introduction
-
-Have you ever had the problem that a user has opened the game page, but before the user logs in to the account, some technical issue causes the user to churn.
-
-Here's a real-world example of how we used custom event analysis to complete the "user churn" process from opening the App to creating a role.
-We designed our analysis as follows:
-
-**1. Be clear about the goal of analytics** : understand user churn before personas are created;
-
-**2. Clarify the process** : Break down the process from the user opening the App to creating a role:
-
-- Click on the game icon
-- Unity initialization
-- The Android Storage Permissions Allow screen appears
-- SDK initialization (record point of "Add Device")
-- The privacy agreement confirmation box pops up [select "No" to exit, SDK initialization fails].
-- Check version
-- Confirm download button (in 4G environment) / Automatic download in WIFI environment
-- Start downloading resources
-- Resource downloading in progress
-- When the resource download is complete, enter the login screen
-- Click TapTap login button
-- A pop-up window will appear, you can choose Tap to open or Tap Accelerator to open
-- When the selection is complete, a pop-up will appear to recall the login authorization
-- Click Agree and return to the game
-- TapTap login completed (the record point of "Convert Device")
-- Game server side to authenticate users
-- Login to the game server
-- Enter a nickname to create a character
-- Create successfully and enter the game
-
-**3. Define the analysis indicators**: According to the above process, with funnel thinking, we designed several key indicators as:
-
-- Resource confirmation download rate (users who confirmed the download / users who updated the pop-up window).
-- Resource download success rate (users who downloaded successfully / users who started downloading the resource).
-- TapTap login rate (users who logged in successfully / users who clicked to log in).
-- Character creation rate (users who successfully created a character / users who logged in successfully).
-
-**4. Explicit Event ** : In general, there are three types of event:
-
-| Event Type | Description |
-| ---- | ----------------- |
-| Exposure events | Exposure of XX pages, exposure of XX pop-ups |
-| Click events | XX button clicks |
-| System events | Initialization, version checking, resource download, etc. |
-
-In the previous step, we identified the analysis metrics. Next we determine the event name and event type:
-
-- Exposure: [Update Alert Pop-up Window]
-- Click: [Update prompt pop-up window - Confirm download button]
-- System Event: [Update Download Success]
-- Click: [Home - TapTap Login Button]
-- System Event: [Login Success]
-- System Event: [Successful Character Creation]
-
-**5. Define event properties**.
-Based on the event names and event types, we have compiled a more complete buried document as follows.
-
-| Event Name | Event Display Name | Property Name | Property Display Name | Property Value | Event Trigger Timing |
-| ---------------- | ---------------- | ----------- | ----- | ---------------- | --------- |
-| pv_download | Update alert pop-ups | #ts | Timestamp | | Triggered when pop-up window is displayed |
-| click_download | Update prompt pop-up window, confirm download button | #networtype | Network Type | WiFi、4g、5g、3g、2g | Triggered when button is clicked |
-| download_success | Update downloaded successfully | #ts | Timestamp | | System background trigger |
-| login_start | Home - TapTap Login Button | #ts | Timestamp | | Triggered when button is clicked |
-| login_success | Login successful | #ts | Timestamp | | System background trigger |
-| create_role | Create Character | #ts | Timestamp | | Triggered when character creation is successful |
-
-With the above steps, we have completed the design of the buried point.
-
-Then we fill the buried point information (event name, event display name, attribute name, attribute display name) into TapDB's "Configuration" - "Event Management".
-
-Then develop according to the buried point document, and then verify the data.
-
-After the buried point is put on line, we use "Event" and "Dashboard" functions to analyze the process of "users losing users from opening the app to actually creating roles", as shown in the following figure.
-
-![](/img/customEvent/49e3e7c0d12cd20cdd4f4aed2c8d0044.png)
-
-The conversion rate of "Update Download Successfully (Step 3)" to "Home - TapTap Login Button (Step 4)" is very low, so we need to perform detailed data analysis. Click [TapDB User's Guide](/sdk/tapdb/features/custom-event/event-analyse) to see the detailed data analysis method.
-
-## 2. buried design ideas
-
-It is recommended to refer to the following steps for buried point design.
-**1. Determine the analysis scenario**.
-**2. Clarify the process**.
-**3、Define analytics metrics**.
-**4. Define the event: design the Event, determine its type, name and other details**.
-**5. Define Event properties: specify the required information and use event properties for reporting**.
-
-## 3. Other design tips
-
-### 3.1 Similar abstraction of Event
-
-When designing events, you may encounter the following problems.
-
-1. To count the passing of three levels A, B and C, should we design a passing event for each level?
-2. When designing the buried point of "apply for verification code" event, there is an event of "apply for verification code" in user registration, user login, password change and other scenarios. Do I need to design an event for each scenario?
-
-We can determine whether the same Event is set for different scenarios based on the same kind of abstraction principle:
-
-- Important events or events of special interest can be treated as separate Events.
-- Events of general importance, such as those that only require analysis of the number of participants and counts, can be set up as a single event with multiple similar behaviors, and specific behaviors can be identified through properties.
-
-1. Events for different levels: if we count the passages of three levels A, B and C, instead of counting them three times, we only need to design a "level pass" event, and then use the attributes to distinguish the three levels A, B and C. 2.
-
-2. Buried function of "apply for verification code": you can define a "apply for verification code" event, and use the attribute value to distinguish the "scenes".
-
-### 3.2 Event naming convention
-
-When designing event display names, ensure that there is no ambiguity about the event. Naming the events in the form of "page name - module name - specific event name" can help analysts understand the events accurately. It is important to avoid incorrect analysis conclusions triggered by inaccurate descriptions. Maintaining uniformity in App page naming makes sense and helps us ensure that everyone's understanding is consistent.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/dimension-table.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/dimension-table.mdx
deleted file mode 100644
index 3d321b94f..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/dimension-table.mdx
+++ /dev/null
@@ -1,125 +0,0 @@
----
-title: Dimensional Table Properties
-sidebar_position: 13
----
-
-## 1. Overview
-
-For event properties and user properties that have already been reported, the originally uploaded data can be mapped to another presentation or calculated value by uploading a dimension table so that the initial buried property value is not the same as the presentation value.
-
-Compared to virtual properties, dimension table properties can bring in information from outside the system that is not contained in the originally uploaded data, instead of just logical transformation based on data within the system.
-
-## 2. Applicable Roles and Uses
-
-| Role | Usage |
-| :------------------------ | :------------------------------- |
-| Burial Designer (Data Product Manager / Analyst) | Avoid excessive redundant fields in burial design, improve data model paradigm and burial design flexibility |
-| Data Analysts (Data Product Managers / Analysts / Operators) | Self-supporting the introduction of external system data as analysis dimensions to meet more personalized analysis needs |
-
-
-## 3. Creating Dimension Table Properties
-
-You can create a dimension table property in the "Dimension Table" column of Event Property Management and User Property Management.
-
-Dimension table properties can be created based on preconfigured properties, custom properties and virtual properties, but not the following types of virtual properties.
-1. virtual attributes created based on dimension table attributes.
-2. virtual event attributes created based on user attributes.
-
-![Create dimension table attributes](/img/customEvent/dimension_table_1.png)
-
-### 3.1 Uploading
-
-Select the "Upload" dimension table property on the base property field where the dimension table needs to be added.
-
-![Upload Inlet](/img/customEvent/dimension_table_2.png)
-
-### 3.2 File encoding requirements {#encoding}
-
-The system supports `CSV` files in `UTF-8` or `UTF-8 with Bom` encoding format, with a file size of no more than 2G.
-
-When using Excel, select the "**CSV UTF-8 (comma separated) (.csv)**" format when saving the document.
-
-![Excel](/img/customEvent/dimension_table_7.png)
-
-When using WPS, select the "**CSV (comma separated) (*.csv)**" format when saving the document.
-
-![WPS](/img/customEvent/dimension_table_8.png)
-
-The file can also be saved in `UTF-8` or `UTF-8 with Bom` encoding format using text editing tools such as Sublime, NotePad ++, etc.
-
-![Sublime、NotePad ++ 等](/img/customEvent/dimension_table_9.png)
-
-### 3.3 File Format
-
-The first row content will be used as the field name of the dimension table property, which can only contain English, numbers or `_`, and needs to start with English.
-
-The first column content will be associated with the original attribute, and the first column value needs to correspond to the original attribute value. If duplicate values are encountered, the first column will prevail and subsequent duplicate information will be discarded.
-
-The dimension table properties do not exceed 10 columns, and the content will be adapted to the data type of the property entered, with the following rules.
-
-| selected data type | file content |
-| :----- | :----------------------------------------------------------- |
-| Text | Any content is allowed |
-| Numeric | Any number, starting with 0 will remove the 0.
-| time | timestamp, or yyyy-MM-dd HH:mm:ss.SSS, yyyy-MM-dd HH:mm:ss, yyyy-MM-dd |
-| boolean | true or false |
-
-For rows where the selected data type does not match the file content type, the row will be discarded completely, so please take care to keep the data type consistent.
-
-### 3.4 Fill in the display name and field type
-
-Fill in the display name and data type of the mapped dimension table fields as required.
-
-![Fill in display name and field type](/img/customEvent/dimension_table_3.png)
-
-### 3.5 Parsing Results
-
-Display the total number of rows parsed, the number of successful rows, the number of rows discarded in error and the reason for the error discard.
-
-![parsing results](/img/customEvent/dimension_table_4.png)
-
-### 3.6 Replacement
-
-If the parsing result is not as expected, the file can be updated and replaced.
-
-![Replace](/img/customEvent/dimension_table_5.png)
-
-## 4. Use of dimension table properties
-
-### 4.1 Managing dimension table properties
-
-Dimension table properties can be managed in the Event Property Management or User Property Management pages.
-
-Dimension table properties are collapsed in the base property column and can be expanded for further manipulation.
-
-! [Manage dimension table properties](/img/customEvent/dimension_table_6.png)
-
-### 4.2 Considerations for use in models
-
-Dimension table properties are used in the same way as usual properties, with their calculation logic and filtering conditions determined by their type.
-
-Dimension table properties of event properties can be used in the events associated with their base properties.
-
-Dimension table properties of user properties are used in the same scenarios as general user properties.
-
-## 5. Best Practice
-
-### 5.1 Improve buried design flexibility
-
-When collecting users' browsing and ordering information from the game store, only product IDs need to be collected; information such as product names and selling prices can be used to create dimension table properties through "product IDs" to meet analysis requirements.
-
-| Product ID (Base property) | (Dimension table property) | Price (Dimension table property)
-| :---------- | :------ | :-------- |
-| 1 | Refill Ticket | 50 |
-| 2 | World Channel Speakers | 10 |
-
-### 5.2 Introduction of external information as analysis dimension
-
-The system collects the model information of user devices, combined with the information of the selling price of each model in e-commerce platforms collected through crawlers, so as to get the grade of each model, which is used to identify the value of users.
-
-| Model (basic property) | Price (dimension table property) | User value (dimension table property)
-| :------- | :-------- | :---------- |
-| model_1 | 999 | low |
-| model_2 | 1299 | Low |
-| model_3 | 1999 | medium |
-| model_4 | 4999 | high |
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/distribution-analyse.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/distribution-analyse.mdx
deleted file mode 100644
index 09304aec0..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/distribution-analyse.mdx
+++ /dev/null
@@ -1,60 +0,0 @@
----
-title: Distribution Analysis
-sidebar_position: 6
----
-
-## 1. Case Introduction
-
-Player payment is a very important in-game behavior, and we want to know the distribution of the total amount paid by players in the game in the recent week. For example, we want to know the number of users in the range of $0-$100, $101-$200, $201-$300, etc. At this time, we can use the function of "Distribution Analysis" to show it.
-
-**1. Setting the event**: we select the event "user pays" and set a custom interval, using 100 as a segment.
-
-![](/img/customEvent/distribution/fenbu-1-1.png)
-
-**2. Setting the dimension**: we focus on the daily change of the amount distribution, so we choose "time of event" in the dimension.
-
-![](/img/customEvent/distribution/fenbu-1-2-2.png)
-
-**3. Set display results**: set the time period and other parameters, and click query to view the analysis results.
-
-![](/img/customEvent/distribution/fenbu-1-2-3.png)
-
-**4、Save report**: save the query results as a report, and then create a dashboard based on the report. A dashboard makes it easy to view analysis results.
-
-![](/img/customEvent/distribution/fenbu-1-4.png)
-
-## 2. What Is Distribution Analysis
-
-Distribution analysis function is mainly used to understand the frequency of events in different zones, the cumulative sum of different event calculation variables, and the distribution of the number of users in different zones such as the length of page views.
-
-## 3. Use Cases For Distribution Analysis
-
-Typical user cases of funnel analysis:
-
-
-- Did the number of users playing the game per day increase after the new version was launched?
-- What are the differences in the number of users from different advertising channel sources in different amount ranges, e.g. range of $0-100, $101-200, $201-300.
-- What are the differences in the distribution of hours played by different levels of players (new players, average players, heavy players).
-
-
-## 4. How To Do Distribution Analysis
-
-There are 4 steps in distribution analysis: setting events, setting dimensions, setting display results, and saving reports.
-
-The last three steps are described in detail in Event Analysis, so here we focus on setting events.
-
-On the distribution analysis page, click "Select Indicator" to see the indicator selection screen. Click Event Analysis to see how to set indicators. Click the "Settings" button to set the display type of calculation results as "Discrete", "Default interval" and "Custom interval".
-
-* **Discrete**: the system will display the distribution values under each number.
-* **Default interval**: the system will display the default interval distribution based on the calculation results.
-* **Custom interval**: supports setting the starting and ending values of each interval segment.
-
-![](/img/customEvent/distribution/fenbu-1-1.png)
-
-## 5. Distribution Analysis Calculation Principle
-
-There are 2 types of statistics for distribution analysis, statistics by count and statistics by event attributes.
-
-**Statistics by number**: statistics on the number of times a user performs an action in a day/week/month.
-
-**Statistical Indicators by Event Properties**: Statistics on the value of a statistical indicator for properties of an event that occurred in a day/week/month. The statistical indicators for the properties are the same as for the event analysis, with sum, mean, maximum, minimum, and de-duplicated numbers.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/event-analyse.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/event-analyse.mdx
deleted file mode 100644
index eab61c033..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/event-analyse.mdx
+++ /dev/null
@@ -1,272 +0,0 @@
----
-title: Event Analysis
-sidebar_position: 3
----
-
-## 1. Case Introduction
-
-We often need to analyze "New Users" and "DAU", and we can use the Event Analysis function to analyze these two indicators.
-
-**1. Determine the indicators and screening criteria**: To analyze DAU and new users, display them on both iOS and Android.
-
-Click on "Select Indicator", in the pop-up window we select the event and filter criteria, and rename the indicator (we can also set the filter criteria in the main screen of event analysis).
-
-![](/img/customEvent/event/event-2.png)
-
-**2. Determine the dimension**:The iOS and Android platforms are the dimensions we analyze, so we select "Device System Type" in "
-Select dimension".
-
-![](/img/customEvent/event/event-3.png)
-
-**3. Select time period**:We need to focus on the last 30 days of data changes, so select "Past 30 day" in the date selector.
-
-![](/img/customEvent/event/event-4.png)
-
-**4. Select a comparison time period**:We use the last 30 days of data changes, compared to the previous month.
-![](/img/customEvent/event/event-5.png)
-
-After setting the conditions, click "Inquire" to output the results.
-
-![](/img/customEvent/event/event-6.png)
-
-**5. Save Report**:We save the query results as a report. The reports are then transformed into a dashboard, which makes it easy to see the final report data every day.
-
-![](/img/customEvent/event/event-7.png)
-
-**These are the steps to view daily users through event analysis**。
-
-## 2. What Is Event Analysis
-
-Event Analysis is to analyze the what, how, when, and where of an event.
-
-## 3. Use Cases For Event Analysis
-
-1. Number of people: how many people triggered the event.
-
-- Trends in UV and DAU;
-- For example, the description of a button is adjusted, before the adjustment is A, after the adjustment is B. Conduct a control test to analyze the click situation of A and B.
-
-2. Times: how many times a certain event was triggered, such as the number of passes on a level per day.
-
-
-3. Per capita times: how many times an event (behavior) is triggered on average.
-
-4. Active ratio: the ratio of the number of people who trigger an event to all active people in a time interval.
-
-5. Data under the event segmentation dimension: for example, by device type, we can see how many button clicks happened on different devices.
-
-6. Four calculations for complex indicators: What happens when the data we want isn't in the reported data? We can add, subtract, multiply and divide indicators to get a new indicator, this way is called custom indicator, such as ROI, ARPU.
-
-## 4. How To Do Event Analysis
-
-Just like the 5 steps mentioned in the case above:
-
-**1. Determine the indicators and screening criteria**
-
-**2. Determine the dimension**
-
-**3. Select time period**
-
-**4. Select a comparison time period**
-
-**5. Save Report**
-
-### 4.1 Select Indicator
-
-When determining the Indicator, it is also necessary to determine whether to enable "Approximate calculation".
-
-#### 4.1.1 Approximate Calculation
-
-When the Approximate calculation is turned on, only some of the samples are queried, with 99.9% accuracy and faster query speed.
-
-![](/img/customEvent/event/event-2-1.png)
-
-You need to click the "Inquire" button to make it effective.
-
-#### 4.1.2 Select Indicator
-
-In the Select Indicator screen, it includes "Select Event", "Select Property", "Rename", "Copy indicator
-", "Switch into indicator formula" and "Indicator screening" functions, as shown below:
-
-![](/img/customEvent/event/event-2-2.png)
-
-**Select Event**:Drop-down option to select all pre-set events and custom events.
-
-**Select Properties or Indicator Name"**: The drop-down option displays the analysis dimensions and Properties of the event, as follows.
-
-![](/img/customEvent/event/event-2-3.png)
-
-A. Analysis indicators: any event has at least these three analysis indicators: total number of times, number of triggering users, number of times per capita.
-
-B. Properties: event properties.
-
-C. Properties analysis indicators: According to the Property value type, the following analysis indicators can exist:
-
-
-| Value Type | Analysis Perspective |
-| ------------------------------------------------------------- | ------------------------- |
-| Number | Total, median, mean, maximum, minimum, per capita, deduplication |
-| List | Deduplicating lists, deduplicating list elements |
-| Boolean | Deduplicated, true, false, null, not null |
-| Non-numeric, Boolean | Deduplicated |
-| Time (supported formats are `yyyyy-MM-dd HH:mm:ss` or `yyyyy-MM-dd HH:mm:ss.SSS`) | Deduplicated |
-
-**Rename **: Take care to use an easy-to-understand description.
-
-**Switch into indicator formula**: Click to switch indicator formula. This feature is useful for special scale analysis scenarios, such as:
-
-- The ratio of active users that day to active users that month. In this scenario, the number of active users in the month is used as the denominator to participate in the calculation.
-- The ratio of the number of paying users for the day to the number of active users for the selected time frame. In this scenario, the total number of active users in the selected time range was used as the denominator to participate in the calculation.
-
-For example, we build a pay rate indicator by using the indicator formula
-
-- A custom indicator named "Paid Rate".
-- Set the event and indicator formula "user payment - number of triggered users" / "account login - number of triggered users".
-- After setting the metric formula, we can choose "percentage", "two decimal" and "integer" presentation styles, we choose two decimal.
-
-![](/img/customEvent/event/event-2-4.png)
-
-#### 4.1.3 Indicator Screening
-
-It is used to set the restrictions of indicators. You can set the filter for individual indicators in the "Indicator Selection", or set the global filter in the main event analysis screen.
-
-![](/img/customEvent/event/event-2-5.png)
-
-##### 4.1.4.1 Screening Data Types
-
-There are 5 types, each supporting a different mathematical logic.
-
-1. Number. e.g.: recharge amount. Support mathematical logic: equal to, not equal to, less than, greater than, with value, without value, interval.
-
-2. String. For example: the city where the event occurred. Support mathematical logic: equal to, not equal to, contains, does not contain, has value, no value, regular match.
-
-3. List ID. e.g.: list. Support mathematical logic: presence element, non-existent element, element position, with value, without value.
-
-4. Time. e.g.: registration time (yyyy-MM-dd HH:mm:ss.SSS or yyyy-MM-dd HH:mm:ss). Support mathematical logic: absolute time, relative to the current time, relative to the moment of the event, with value, without value。
-
-5. Boolean. e.g.:: Wifi use. Support mathematical logic: true, false, with value, without value.
-
-##### 4.1.4.2 The difference between equal to / not equal to and including / not including
-
-Equal/Not equal: filter items that strictly match the selected value. For example, if the sub-continent is equal to America, then only the data for the Americas will be queried.
-
-Includding/Not including: the filter item contains the selected value can be filtered, such as the subcontinent contains the Americas, then the data of the Americas, North America, South America can be filtered out.
-
-Absolute time: refers to the objective real time. For example, 2021-03-02 19:24:52 to 2021-03-08 19:24:52, the former time must be before the latter time. Click "Select time" to select seconds.
-
-![](/img/customEvent/event_analyse_time_filter.png)
-
-Relative current time: The last n days relative to the present.
-
-![](/img/customEvent/event_analyse_relative_time_1.png)
-
-Relative event time: The n days before and after the selected event. A negative number can be selected here, such as -1 day, which means the day before the selected event, and a positive number, such as 1 day, which means the day after the selected event.
-
-![](/img/customEvent/event_analyse_relative_time_2.png)
-
-##### 4.1.4.3 "And" & "Or"
-
-Multiple filters can be added. When there are at least 2 or more filters, the And / Or toggle button will appear, and the default is "And". "And" is the intersection of multiple filters, and "or" is the concatenation of multiple filters.
-
-After selecting the filter, click the query button to take effect.
-
-### 4.2 Select Dimension
-
-#### 4.2.1 Dimension
-
-A dimension is a decomposition of facts that splits metrics to observe patterns:
-
-"Country where the event occurred" is a dimension that groups the facts by the country where they occurred, thus observing the pattern of the indicator;
-
-The "age of the user base" is a dimension that looks at payment data for users in different age groups;
-
-"Channel package" is a dimension that looks at the retention data of users under different channel packages;
-
-Time is the most fundamental dimension.
-
-#### 4.2.2 Classification Of Dimensions
-
-The dimension drop-down allows you to select event properties, user properties, and user clusters:
-
-- Event properties: describe the state at the time of the event, e.g. : How much did the user charge in the United States? Here we query for payment events that occurred in the United States (the user who charged the money may not be in the United States right now).
-- User properties: describe the state of the user that triggered the event. For example: How much money are users in the US currently charging? Here we query for paid events triggered by users who are now in the United States (including those who were not in the United States).
-- User clusters: users belonging to the selected subgroup.
-
-![](/img/customEvent/event/event-2-6.png)
-
-Differences in data logic:
-
-- Event properties: Parameters for each event. For example, the "Mid-Autumn Festival" event is transmitted through the SDK, which has three properties: "the number of Mid-Autumn Festival props", "the type of zongzi" and "the number of dragon boats purchased by the user". When selecting the dimensions of an event property, the available dimensions are constrained by the selected event, for example, if the event is selected as "Mid-Autumn Festival", the event property can only select these three.
-- User attributes: Fields in the user table. User attributes are not constrained by the selected event.
-
-When the selected dimension is a numeric field, such as payment amount, there will be the selection of interval. The discrete interval means that the aggregation query is carried out according to all the values of the field. The default interval is the aggregation interval set by the system according to a certain rule, and the custom interval is that the customer can define the interval segment to aggregate data.
-
-![](/img/customEvent/event_analyse_custom_range.png)
-
-When the selected dimension has "event occurrence time", the time can be selected to support by day, hour, minute, week, month.
-
-![](/img/customEvent/event_analyse_time_granularity.png)
-
-### 4.3 Select Date
-
-Select the date we care about.
-
-![](/img/customEvent/event/event-2-7.png)
-
-### 4.4 Select a comparison date
-
-For example, in the example below, if the selected date is 2021-3-2 to 2021-3-8 with an interval of 6 days, then the comparison date interval is also 6 days. When selecting a comparison date, you only need to select one date (e.g. 2021-02-23) and the system will automatically project back 6 days (to 2021-03-01).
-
-![](/img/customEvent/event_analyse_time_compare.png)
-
-### 4.5 Save Report
-
-After selecting the main dimensions and indicators, click Save Report to save the report, enter the name and confirm, then the report is saved to Saved Reports.
-
-![](/img/customEvent/event_analyse_save_report_1.png)
-
-After saving a report for the first time, when you open the report again, the "Save As" button will appear and you can save it as a new report, if you click "Save Report", you are updating the original report.
-
-![](/img/customEvent/event_analyse_save_report_2.png)
-
-The saved reports are displayed in My Reports on the right side of the screen.
-
-![](/img/customEvent/event_analyse_save_report_3.png)
-
-## 5. Pivot Table
-
-A pivot table is an interactive form of data reporting that features calculations related to the arrangement of data and pivot tables. It is called a pivot table because the order of the aggregated dimensions of the data can be dynamically changed, thus allowing customers to view the data from multiple perspectives. Below we compare the following two pivot tables.
-
-![](/img/customEvent/event_analyse_pivot_table_1.png)
-
-![](/img/customEvent/event_analyse_pivot_table_2.png)
-
-
-In the first table, we want to query the sum of the recharge amount of users in each province, each device system and each network operator in China. From the perspective of "province", the data of "equipment system" is split, and then the data of "network operator" is split.
-
- In the second table, we want to query the total recharge amount of users in each network operator, each device system, and each province. From the perspective of "network operator", the data of "equipment system" is split, and then the data of "province" is split.
-
-### 5.1 Draggable
-
-Each column of a pivot table can be dragged and dropped with the mouse to change its order. After the dimensions are dragged out of order, the data re-enters the query. Flexible use of drag and drop can improve the efficiency of data query. Currently, pivottables can be ticked up to 5 dimensions and 20 indicators.
-
-### 5.2 Search
-
-In the dimension header of the pivot table, you can click the search input text, and the search function can help quickly filter out the eligible items. The query is not re-entered after filtering.
-
-
-![](/img/customEvent/event_analyse_table_filter.png)
-
-### 5.3 Sort
-
-Default sorting logic for pivottables:
-
-Firstly, the total data of the first column of the main dimension index was sorted from large to small.
-
-Then, the total data of the second column of the main dimension index is sorted from large to small.
-
-And so on, sort the totals for the NTH main dimension indicator from large to small.
-
-### 5.4 Download Data
-
-Click the download button on the right side of the pivot table to download the tiled data, and the downloaded result is the current query result. What you see is what you get. Available in CSV/PDF/picture format.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/funnel-analyse.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/funnel-analyse.mdx
deleted file mode 100644
index be4031bd9..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/funnel-analyse.mdx
+++ /dev/null
@@ -1,146 +0,0 @@
----
-title: Funnel Analysis
-sidebar_position: 5
----
-
-## 1. Case Introduction
-
-In [buried design guide](/sdk/tapdb/features/custom-event/data-model), we designed the buried process based on the transformation process from "user opening the app to actually creating a persona", and here we analyze this transformation process with the "funnel analysis" function.
-
-**1. Set up steps**: create events for the entire process from the user opening the App to the actual creation of the role, and set up funnel steps in the order of the user's actions.
-
-![](/img/customEvent/funnel/案例-1.png)
-
-**2. Setting the dimension**: different system types may have an impact on the conversion situation, so select "device system type" in the dimension.
-
-![](/img/customEvent/funnel/案例-2.png)
-
-**3. Setting the funnel period**: we set the funnel period to 7 days.
-
-![](/img/customEvent/funnel/案例-3.png)
-
-**4. Set display results**: set up time periods and other parameters and click on the query to view the analysis results.
-
-![](/img/customEvent/funnel/案例-4.6.png)
-
-**5. Save report**: Save the query results as a report, and then create a dashboard based on the report. A dashboard makes it easy to view analysis results.
-
-![](/img/customEvent/funnel/案例-5.2.png)
-
-## 2. What Is Funnel Analysis
-
-Funnel analysis is to analyze the conversion rate of users from the starting point to the end point, which is used to measure the conversion effect of each node. In an ideal situation, the user will follow the path of the product design to reach the final target event, but the reality is that the user's behavior path is diverse.
-
-
-By configuring the critical business path through buried events, it can analyze the user conversion and churn. This function can not only identify potential problems with the product, but also locate the loss of users in each link, so as to facilitate the subsequent conversion through product means or marketing means.
-
-## 3. Use Cases For Funnel Analysis
-
-Typical user cases of funnel analysis:
-
-- There is a lot of traffic, but few registered users. What part of the process is the problem?
-- What is the overall conversion rate from sign-up - character creation - play - pay?
-- What are the differences in conversion rates between regions?
-- Two promotion channels bring different users. Which channel has the highest conversion rate?
-
-## 4. How To Do Funnel Analysis
-
-There are 5 steps in the funnel analysis.
-
-**1. Set up steps**
-
-**2. Setting the dimension**
-
-**3. Setting the funnel period**
-
-**4. Set up display results**
-
-**5. Save report**
-
-### 4.1 Setting the funnel
-
-On the Funnel Analysis page, click "Set Steps" to see the "Add Funnel Step" screen.
-
-![](/img/customEvent/funnel/exp/1-漏斗分析-设置漏斗.png)
-
-In the "Add Funnel Step" screen, you can select a funnel step and add a funnel step by.
-
-1. Step: consists of an event (one or more filter conditions can be added) that represents a critical step in a conversion process.
-
-A funnel contains at least 2 steps, each containing one event. More steps can be added, and the order of the steps can be changed by dragging the serial number in front of the step.
-
-It is also possible to set filter conditions for the steps. You can also set global filtering in the main interface of funnel analysis.
-
-2、Add steps: Add more steps to the funnel to be analyzed.
-
-![](/img/customEvent/funnel/exp/2-漏斗分析-设置漏斗.png)
-
-### 4.2 Set Dimension
-
-In the dimension drop-down box, the display includes event attributes (Step 1), user attributes, and user subgroups.
-
-![](/img/customEvent/funnel/exp/3-漏斗分析-选择维度.png)
-
-### 4.3 Set the funnel cycle
-
-**Funnel Cycle**: from the time the user triggers Step 1, the completion of the subsequent steps within the window period counts as the conversion of the subsequent steps.
-
-![](/img/customEvent/funnel/exp/4-漏斗分析-漏斗窗口期.png)
-
-**Limiting the window period within the time interval**: only within this time frame, the user travels from the first step, to the last step, is considered a complete funnel conversion.
-
-### 4.4 Set Display Results
-
-You can select the range of analysis steps, and according to the "Comparison/Trend" and "Conversion/Churn" settings, you can get 4 types of reports: Conversion Comparison Table, Churn Comparison Table, Conversion Trend Table, and Churn Trend Table.
-
-![展示结果](/img/customEvent/funnel_analyse_result_type.png)
-
-Conversion comparison table: to analyze the cumulative conversion rate from step 1 to the subsequent steps.
-
-![转化对比表](/img/customEvent/funnel_analyse_table_1.png)
-
-Churn comparison table: to analyze the churn rate between each step.
-
-![流失对比表](/img/customEvent/funnel_analyse_table_2.png)
-
-Conversion trend table: to analyze the trend of conversion rate by date.
-
-![转化趋势表](/img/customEvent/funnel_analyse_table_3.png)
-
-Churn trend table: to analyze the trend of attrition rate by date
-
-![流失趋势表](/img/customEvent/funnel_analyse_table_4.png)
-
-### 4.5 Save to Dashboard
-
-Save the query results as a report, and then create a dashboard based on the report. A dashboard makes it easy to view funnel analysis results.
-
-![](/img/customEvent/funnel/exp/5-漏斗分析-保存报表.png)
-
-## 5. Funnel Analysis Principle
-
-The funnel analysis will be explained in detail next, especially when there are grouping and filtering cases, the calculation principle will be more complicated.
-
-### 5.1. Basic calculation principle
-
-Suppose a funnel consisting of steps 1, 2, 3, 4, and 5 is selected for the time range March 1, 2021 to March 7, 2021, with a window of 3 days. If a user triggers step 1 from March 1, 2021 to March 7, 2021, and triggers steps 2, 3, 4, and 5 in sequence within the 3 days of step 1, the user is considered to have completed a complete funnel transformation, and if steps 1 > 2 > 4 > 5 are triggered in sequence, the user has completed only step 1 > 2.
-
-If the steps are interspersed with some other steps, such as 1 > X > 2 > X > 3 > 4 > X > 5 (where X represents other events), the user is still considered to have completed a complete funnel transformation.
-
-When a user has multiple events in the selected time period that all meet the definition of a certain conversion step, the event closer to the final conversion goal is preferred as the conversion event, and the conversion calculation stops when the final conversion goal is reached for the first time.
-
-Assuming that the steps of a funnel are defined as: Browse Mall, Select Prop, Generate Order, Pay Success, then the sequence of different user behaviors and the actual conversion steps (bolded parts) are as follows.
-
-Example 1: **Browse mall** > Select prop (prop B) > **Select prop (prop A)** > **Generate order** > Pay successfully
-
-Example 2:Browse the mall > Select prop (prop B) > **Browse the mall** > **Select prop (prop A)** > **Generate order** > **Payment success**
-
-Example 3:Browse mall > Select prop (prop B) > **Browse mall** > **Select prop (prop A)** > **Generate order** > **Payment successful** > Select prop (prop A) > Generate order > Payment successful
-
-The numbers presented in the funnel analysis represent the number of unique users converted/lost, not the number of events triggered. Within this time frame, even if a user completes the funnel multiple times, it is only counted once.
-
-### 5.2 Grouping and Screening
-
-The grouping and filtering of funnel analysis are based on the users who have completed conversions/lost.
-Grouping and filtering based on user attributes and user subgroups: Grouping and filtering based on user attributes and user subgroups based on users who have completed conversion/lost.
-Grouping and filtering based on event attributes: Grouping and filtering based on the event attributes of the user in step 1 based on the users who completed conversion/lost.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/kanban.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/kanban.mdx
deleted file mode 100644
index 421fd46b4..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/kanban.mdx
+++ /dev/null
@@ -1,111 +0,0 @@
----
-title: Dashboard
-sidebar_position: 8
----
-
-## 1. Overview
-
-Dashboard is a collection of multiple reports. After saving the constructed indicators, retention, funnels, and so on as reports, you can add them to the dashboard to help you monitor daily data.
-
-![概述](/img/customEvent/kanban_summary.png)
-
-## 2. Using Dashboard For Data Analysis and Collaboration
-
-In this section, we will use the demo project as an example to demonstrate the process from creating a new dashboard to collaborating through dashboard.
-
-### 2.1 Dashboard Page
-
-![进入看板](/img/customEvent/kanban_layout.png)
-
-The dashboard page is divided into three parts: "Directory, Settings, and Report Display".
-
-1. Directory: you can create folders to view self-built dashboard or shared dashboard of team members.
-2. Settings: setting items include adding reports, setting sharing, adjusting settings, refreshing dashboard, setting global filtering, etc.
-3. Report Display: used to display each report information. Support drag and drop reports to sort, support custom size, support chart display.
-
-### 2.2 New Dashboard
-
-Now, we need to form some core indicators into daily reports, so aggregate the daily core indicators reports in dashboard.
-
-![新建看板](/img/customEvent/kanban_create_1.png)
-
-![新建看板](/img/customEvent/kanban_create_2.png)
-
-We click "+" in the upper right corner of the left column of the dashboard page, select "Add a board" and name it "Daily Board".
-
-### 2.3 Edit, Rename, Delete Dashboard
-
-![编辑、重命名、删除看板](/img/customEvent/kanban_operation.png)
-
-Dashboard that already exist can be edited or deleted.
-
-### 2.4 Manage Dashboard With Folders
-
-Folders are used to organize the dashboard. We can create, rename, and delete folders, and the system has built-in "ungrouped" folders and "shared to me" folders.
-
-![文件夹](/img/customEvent/kanban_create_folder.png)
-
-Click "+" at the top right corner of the dashboard page, select "Add a folder" and name it "Game Operation" to store the game operation related dashboard.
-
-![移动](/img/customEvent/kanban_move.png)
-
-We can move the previously created dashboard "Daily Board" to the folder "Game Operation".
-
-### 2.5 Adding Reports to Dashboard
-
-Reports are the basic elements of dashboard, and we can create new reports in event analysis, retention analysis, funnel analysis and other analysis model functions.
-
-
-In order to meet the needs of daily reporting, we build reports such as the number of logged-in accounts and app launch devices in event analysis, and reports such as user app launch 7-day retention in retention analysis, and now we add these reports to the dashboard.
-
-![添加](/img/customEvent/kanban_add_report_1.png)
-
-Click the "Reports" button at the top right of the dashboard and click "+" to add a report.
-
-![添加](/img/customEvent/kanban_add_report_2.png)
-
-Click "+" to add a report to the current dashboard.
-
-### 2.6 Setting Reports
-
-![设置](/img/customEvent/kanban_setting_1.png)
-
-![设置](/img/customEvent/kanban_setting_2.png)
-
-For the reports added to dashboard you can adjust the report size, visualization, time filtering, display indicators, display groups, and other information.
-
-You can sort by group indicator value or group name, and set it to check "Top N items". The dashboard will then sort and dynamically change the selected groups based on the data at the time of query.
-
-![看板](/img/customEvent/kanban_function.png)
-
-
-Set the "Number of active accounts and active devices" window size to small, so that we can quickly see the traffic situation of the day.
-
-Set the "Number of active devices by country" window size to medium and select the chart type as "Trend chart", so that you can observe the recent trend of the number of active devices by country.
-
-Set the "Active User Distribution by System" window size to medium, and select the chart type as Distribution, so that you can observe the distribution of users by system.
-
-Set the "App Launch 30-Day Retention" window size to large, so that you can observe the retention data at the same time.
-
-### 2.7 Setting Dashboard
-
-After adding the report to the board, we can update the board and set the "Approximate calculation or not".
-
-![设置看板](/img/customEvent/kanban_refresh.png)
-
-The dashboard is updated regularly by default. The system regularly refreshes the calculation results for the dashboard every day and caches the results for the next view. It is recommended to turn on regular update for scenarios such as "more reports loaded" or "more calculations" in the dashboard to improve the display efficiency.
-
-Dashboard can be set to update in real time, suitable for indicators that need to be refreshed in real time, such as the current day's ad placement data.
-
-Dashboard defaults to exact calculation, meaning that each calculation will calculate the exact value according to the conditions. If you have a need for query efficiency, you can turn on the "Approximate calculation" option, which will adopt the approximate algorithm for data such as the number of triggered users, the number of times per capita, the average value of people, the number of de-weighting, etc., which can greatly reduce the performance overhead and the calculation time.
-
-For the "Daily Dashboard" created by the current demo, we focus more on the recent trends of the main indicators, so we choose to update it regularly at 1am every day and turn on the approximation calculation.
-
-### 2.8 Shared Dashboard
-
-If you want other team members to be able to view the newly created dashboard and even maintain and update it together, you can share the permission to other team members.
-
-The shared permissions are divided into two categories: shared and visible. The two categories are independent of each other and can be granted separately. You can grant shared nd visible permissions to "all users", so that any member who joins the project will have the permission and does not need to update it frequently.
-
-![共享看板](/img/customEvent/kanban_share.png)
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/meta-data.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/meta-data.mdx
deleted file mode 100644
index e5e4ac0c0..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/meta-data.mdx
+++ /dev/null
@@ -1,173 +0,0 @@
----
-title: Metadata Management
-sidebar_position: 10
----
-
-## 1. Overview
-
-"Metadata Management" is the data management module in the system, which is the place to unify the management of metadata.
-
-TapDB adopts metadata pre-registration mode. Before receiving the data reported by SDK, the events and attributes to be collected must be registered into the corresponding function module, and when receiving the data, the system needs to check the metadata according to the pre-registered metadata, and the data that meet the conditions will be stored, and the data that do not meet the conditions will be rejected.
-
-This model can effectively improve the accuracy of data, and this model can solve the problem of inconsistent data reporting and data storage.
-
-Metadata management consists of 3 parts: event management, event attribute management and user attribute management.
-
-## 2. Applicable Roles and Uses
-
-| Roles | Usage |
-| ----------- | -------------- |
-| Administrator | Entering buried scenarios and managing system metadata |
-| Business person | View metadata and understand business implications of data |
-| Client / Front End Engineer | View buried requirements |
-
-
-## 3. Event Management
-
-"Event" is the basic analysis object of various data analysis models in the system, and the system has built-in "preset events" and provides the function of reporting "custom events".
-
-In the "Event Management" function, you can pre-register "custom events" before reporting, and manage "events" from various aspects.
-
-Developers can check the pre-registered custom events that have been reported as "No" and perform buried development.
-
-![事件管理](/img/customEvent/metadata_event_overview.png)
-
-### 3.1 Concepts Explanation
-
-The "concepts" and "explanations" related to "event" are as follows.
-
-| Concepts | Explanation |
-| ---- | ---- |
-| Event name | Unique identifier of the event. |
-| Display name | Display name in the analysis model. |
-| Description | Describe the information of buried points, such as: "trigger timing" to help technical staff more accurate buried points; "business connotation" to help business people more in-depth understanding. |
-| Event types| Event types are divided into "preset" and "custom" categories: **Preset**: system built-in events, widely applicable to all kinds of game projects, only need to turn on the buried switch in the SDK to report **Custom**: custom-created events to meet the personalized needs of game projects, need to be entered in the event management before reporting.|
-| Whether to report | Whether the event has been reported. |
-| Receive Switch | The switch to receive event reports or not. |
-| Status | There are four states: "Normal", "Hidden", "Deleting", and "Deleted". **Normal**: Events that are in a normal state. Hidden: Not displayed in the analysis model. **Deleting**: After the reported data is deleted, the event will enter the "Deleting" status, and the deletion can be withdrawn within 72 hours. During this period, the data is still received but not displayed in the analysis model. **Deleted**: Deleted: After the reported data is finally deleted, the events will be recorded in the metadata management with the status of "Deleted", no more data will be received, no more will be displayed in the analysis model, and the progress of using custom event data will not be occupied. |
-| Event properties | You can choose to bind event properties in New and Edit. Event properties are divided into two categories: "preset" and "custom": **Preset**: The newly created events are in the "debug" state by default and are not displayed in the analysis model. **Custom**: Customized event properties to meet the personalized needs of game projects .|
-| Data limit | To ensure system performance, the number of custom events in "Normal", "Hidden", and "Deleting" states must not exceed 100. "Deleted" custom events do not occupy the usage progress, and you can delete custom events to release the usage progress. |
-
-### 3.2 Create Custom Events
-
-![创建自定义事件](/img/customEvent/metadata_event_create2.png)
-
-Fill in the basic information. By default, all preconfigured properties are associated, and by default, all custom properties are not associated.
-
-Select the custom properties that need to be associated, create new custom event properties if they do not meet the requirements, and untie the preconfigured properties that do not need to be associated. The creation of custom events is completed.
-
-### 3.3 View, Edit, Delete Events
-
-![查看、编辑、删除事件](/img/customEvent/metadata_event_edit.png)
-
-Click on the event name to see the basic information and all associated properties.
-
-In the action bar, you can edit the basic information and change the association with the event properties, but the event name cannot be edited for reported events.
-
-In the action bar, you can delete existing custom events. If a reported event has occurred, it will enter the state of "deleting" and can be revoked within 72 hours. Events that have not been reported will enter the "Deleted" status and will no longer receive data and be displayed in the analysis model, while releasing the progress limit of custom event data usage.
-
-### 3.4 Q&A
-
-Q1:Why some events are deleted without keeping any records, while some events are deleted and left in the metadata management with "deleted" status?
-
-A1: Events that have not been reported do not keep records, while events that have been reported are kept. During the design process of buried points, business people may change the design of buried points many times due to requirement changes, requirement merging, etc. Therefore, before the act of reporting, it can be considered that all the entry acts are in "draft", while once the data is reported, it is equivalent to the final draft, and we hope that the system can faithfully record all the data collection solutions used in the project.
-
-## 4. Event Property Management {#event-props}
-
-"Event Attributes" is the attribute information used to describe "events", TapDB has built-in "Pre-set Event Attributes" and provides the function to report "Custom Event Attributes".
-
-In "Event Attribute Management", you can pre-register "Custom Event Attribute" and manage "Event Attribute" from various aspects.
-
-Developers can check the pre-registered custom event attributes that have been reported as "No" and perform buried development.
-
-![事件属性管理](/img/customEvent/metadata_event_prop_overview.png)
-
-### 4.1 Concepts Explanation
-
-The "concepts" and "explanations" related to "event property" are as follows.
-
-| Concepts | Explanations|
-| ---- | -------------------------------------------------------------------------- |
-| Property name | Unique identifier of the event property. |
-| Display name | Display name in the analysis model. |
-| Description | Describe the attribute information, such as: describe the business connotation, to help business personnel more in-depth understanding. |
-| Data types | The data type of the property and the data matching the type can be entered into the library. |
-| Units | Units of statistical values, which are displayed in analytical models and reports. |
-| Property types | There are two types: "Preset" and "Custom": **Preset**: built-in event properties, applicable to each event in the game project, only need to turn on the buried switch in the SDK to report. **Custom**: custom event properties to meet the individual needs of the game project, need to be entered in the event property management before reporting . |
-| Whether to report| Whether the event property has been reported. |
-| Receive Switch| Switch for the system to receive event properties to report or not.|
-| Status | There are two types: "Normal" and "Hidden": **Normal**: Events that are in a normal state **Hidden**: not shown in the individual analysis models. |
-| Data limit | To ensure system performance, the number of custom event properties should not exceed 300. |
-
-### 4.2 Create Event Property
-
-![事件属性创建](/img/customEvent/metadata_event_prop_create.png)
-
-Fill in the basic information about the event property, which completes the creation of a custom property. You can associate it with an event in Event Management.
-
-### 4.3 View and Edit
-
-![事件属性查看与编辑](/img/customEvent/metadata_event_prop_edit.png)
-
-Click on the property name to see basic information and all associated events.
-
-In the action bar, you can edit other basic information except "Property name" and "Data type".
-
-### 4.4 Q&A
-
-Q1: Why is it not possible to delete a custom event property without reporting any data, but custom events can be deleted?
-
-A1: Adding a new event is just a single record in the table, while adding a new property requires adding a new column in the table, which changes the data structure. Both are very difficult to implement. Therefore, the ability to delete custom attributes is not supported in the current version, and this feature will be added in the future version.
-
-## 5. User Property Management {#user-props}
-
-
-"User attributes" is used to describe the attribute information of "user", TapDB has built-in "preset user attributes" and provides the function of reporting "custom user attributes".
-
-TapDB uses "Account" and "Device" as user identifiers to analyze different subjects respectively, and "Account" and "Device" will be used as part of user identifiers in user attribute management respectively.
-
-In the "User Attribute Management" function, you can pre-register "custom user attributes" and manage "user attributes" from various aspects.
-
-Developers can check the pre-registered custom user attributes that have been reported as "No" and perform buried development.
-
-![用户属性管理](/img/customEvent/metadata_user_prop_overview.png)
-
-### 5.1 Concepts Explanation
-
-The "concepts" and "explanations" related to "user property" are as follows.
-
-| Concepts | Explanations |
-| ---- | ---------------------------------------------------------------------|
-| User ID | One of the identifiers of user properties, divided into two categories: "Account" and "Device". |
-| Property name| One of the identifiers, which together with the "User ID" form a unique identifier. |
-| Display name | Display name in the analysis model. |
-| Description | Describe the attribute information, such as: Describe the trigger timing to help technical staff more accurate buried points; describe the business connotation to help business people more in-depth understanding. |
-| Data types | The data type of the property and the data matching the type can be entered into the library. |
-| Units | Units of statistical values, which are displayed in analytical models and reports. |
-| Property types | There are two types: "Preset" and "Custom": **Preset**: built-in user properties, applicable to each event in the game project, only need to turn on the buried switch in the SDK to report. **Custom**: custom user properties to meet the individual needs of the game project, need to be entered in the event property management before reporting .|
-| Whether to report| Whether the user property has been reported. |
-| Receive Switch| Switch for the system to receive user properties to report or not.|
-| Status | There are two types: "Normal" and "Hidden": **Normal**: Events that are in a normal state **Hidden**: not shown in the individual analysis models. |
-| Data limit | To ensure system performance, the number of custom user properties should not exceed 100. |
-
-### 5.2 Create User Property
-
-![创建](/img/customEvent/metadata_user_prop_create.png)
-
-Fill in the basic information to complete the creation of the custom property. Currently, custom user properties cannot be deleted once they are created.
-
-### 5.3 View, Edit, And Copy
-
-![查看、编辑与复制](/img/customEvent/metadata_user_prop_edit.png)
-
-Click the property name to view the basic information.
-
-In the action bar, you can edit the basic information except "property name" and "data type".
-
-In the action bar, you can copy a custom user property with the same information but a different user identity. The copy function is convenient and quick to synchronize the device and account.
-
-### 5.4 Q&A
-
-Q1: Why is it necessary to distinguish between "device" and "account"?
-
-A1: Identifying users with different subjects is more in line with business needs. For example, in the business of advertising placement, device is a better user identification, while when analyzing users' specific playing behavior, account may be a better user identification. In addition, you can make good use of the copy operation to create user properties for both types of identifiers.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/overview.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/overview.mdx
deleted file mode 100644
index f8b26a7c2..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/overview.mdx
+++ /dev/null
@@ -1,48 +0,0 @@
----
-title: Overview
-sidebar_position: 1
----
-
-The TapDB SDK has a number of preset reporting events that allow us to build a very robust and complete operational module. But as you dig deeper into data analysis, there are obvious limitations to a fully templated, preconfigured analysis model. We designed custom event analytics to give you the freedom to report and query the data you need. There are some barriers to access and understanding, but the ceiling for digging into player behavior with custom event analysis is significantly higher than the pre-built report template, so we highly recommend learning and using it.
-
-## Event Analysis
-
-Event analysis is the most basic analysis model, and its analysis object is events.
-
-- See all the information about the event, such as:
- - See the total number of times the "Play PVP" event was triggered, the number of users triggered, and the number of times per capita.
- - The total amount of purchase and the number of times per person for the "Buy Gift Package" event.
- - The number of "card draw" events per capita in different provinces in the last 7 days.
- - The proportion of TapTap logins and the proportion of wechat logins in daily "account logins" events.
-
-## Retention Analysis
-
-Retention analysis is a model that analyzes the generalized retention behavior of users, and its analysis object is the device/account.
-
-We can observe the user who triggered event A and then triggered event B. The initial event and the return event can be the same, for example:
-- Check the "Login" status of "Paid" accounts within the next 7 days.
-- Check if a "Level 10" account "pays" within the next 7 days.
-- Check the status of "rewards" within 30 days for accounts that have "purchased a monthly card".
-- Check the "login" status of accounts that triggered "login" for the first time and "login" within 90 days afterwards: this is what we usually call "account retention".
-
-## Funnel Analysis
-
-Funnel analysis is a model for analyzing users who trigger specific events in a sequence. It is analyzed for devices / accounts.
-
-- To view information about those users who converted strictly according to the funnel steps, you can set the funnel window period, for example:
- - The number of accounts that "completed check-in" and the number of accounts that "engaged in PVP" within 30 minutes after "completed check-in".
- - The number of accounts that "purchased a monthly card" and the number of accounts that "purchased a monthly card" and then "purchased a newbie pack" within 7 days after "purchased a monthly card".
-
-
-## User Segmentation
-
-User segmentation is an extremely powerful feature in custom event analysis. Here's how it works:
-
-- Find a group of users to be analyzed by looking at the data, e.g. users with very low retention rate, high ARPPU, or consistently signing in but with very low rank, and save them as a user segment.
-- Using this user segment as a condition, we observe or filter the behavioral data to try to find some behavioral characteristics, e.g., users with very low retention rates have Android versions below 7, presumably because of the large number of emulator devices included in the statistics.
-
-User segmentation opens the way from finding features to digging deeper, and is a necessary skill for custom event analysis problems.
-
-## Dashboard
-
-Dashboard is suitable for observing data. It is often used in scenarios such as "forming a daily report of core metrics" and "continuously observing a specific issue". Once you have saved a report in your analytic model, you can add it to Dashboard. Dashboard core ability is sharing: you can share your Dashboard to other users to reduce communication cost. Note that a good Dashboard should have a clear topic to avoid the distraction of combining unrelated information into one Dashboard.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/property-analyse.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/property-analyse.mdx
deleted file mode 100644
index 41801c7d8..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/property-analyse.mdx
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: Property Analysis
-sidebar_position: 7
----
-
-## 1. What Is Property Analysis
-
-
-We often encounter the need to analyze the distribution of players by province, the age distribution of players, and so on. With Property analysis, a user profile can be quickly drawn.
-
-Property analysis is a model used to analyze the statistics and distribution of user properties. The model categorizes users according to their properties and allows you to view the statistics of users under different groupings at the same time. Property analysis is used to analyze the model of the statistics and distribution of user properties. The model categorizes users according to their properties and supports viewing user statistics under different groupings.
-
-Property analysis can help developers understand the characteristics of player groups, help developers understand the composition and preferences of players, and provide a basis for refined operations.
-
-## 2. Use Cases For Property Analysis
-
-Typical user cases of property analysis:
-
-- Analyze the average amount spent by users with different membership levels.
-- Analyze the distribution of players in different provinces.
-
-## 3. How To Do Property Analysis
-
-There are 4 steps in property analysis: setting indicators, setting dimensions, setting display results, and saving reports.
-
-The last three steps are described in detail in Event Analysis, so here we focus on setting indicators.
-
-![](/img/customEvent/character/character1.png)
-
-All types of properties can have "deduplicated number" as the analysis indicator. For numeric types, "Sum", "Mean", "Maximum" and "Minimum" can be used as the analysis indicator.
-
-1. **Number of users:** Number of all users.
-2. **Deduplication:** The number of deduplicates for this property among all users.
-3. **Sum:** The sum of the attribute among all users.
-4. **Mean:** The arithmetic mean of this attribute over all users.
-5. **Maximum:** The maximum value of this attribute among all users.
-6. **Minimum:** The minimum value of this property among all users.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/retention-analyse.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/retention-analyse.mdx
deleted file mode 100644
index ac7ed6acc..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/retention-analyse.mdx
+++ /dev/null
@@ -1,126 +0,0 @@
----
-title: Retention Analysis
-sidebar_position: 4
----
-
-## 1. Case Introduction
-
-Games are usually updated frequently, so what is the change of retention rate after the update, we can use the function of "Retention Analysis" to analyze it.
-
-**1. Setting events**: Since we focus on the overall retention of users to the game, we set the initial event and return event of users as "account login".
-
-![](/img/customEvent/retention/LC-1-1.png)
-
-**2. Set the dimension**: We are concerned about whether version updates have an impact on retention, so select "App version number" in the dimension.
-
-![](/img/customEvent/retention/LC-1-2.png)
-
-**3. Set display results**: setting up time intervals and other parameters.
-
-![](/img/customEvent/retention/LC-1-3.png)
-
-**4. Save Report**: View the analysis results and save the analysis results as a report.
-
-![](/img/customEvent/retention/LC-1-4.png)
-
-## 2. What Is Retention Analysis
-
-Retention is when players stay in your game and keep using it.
-
-Only with good retention can you guarantee that new players will not be lost after registration. Sometimes we only look at the daily activity (DAU), we will think the data is good, but it may be because of the recent intensive operation to attract new users. but the remaining users are not necessarily growing, it may be decreasing, it is just hidden by the number of new users. This is like a basket that keeps leaking, and it is difficult to achieve sustained growth if you do not repair the cracks under it and only pour water into it.
-
-When we talk about retention, we mean the percentage of "target players" who "come back to complete an action" over a period of time. Common indicators are next-day retention, seven-day retention, next-week retention, and so on. For example, the "next-day retention rate" of a "new player" acquired at a given time is often used to measure the effect of attracting new players.
-
-## 3. Use Cases For Retention Analysis
-
-1. The number of players who have completed the login (initial event) and have made a top-up operation in the next month (return event).
-2. How many players who upgraded to VIP level 9 (initial event) purchased gift packs in the coming month (return event).
-3. To determine whether a game update is working or not. For example, observe whether someone uses the game for a few more months because of the new hero character.
-
-## 4. How To Do Retention Analysis
-
-There are 4 steps in the retention analysis.
-
-**1. Setting events**
-
-**2. Set the dimension**
-
-**3. Set display results**
-
-**4. Save Report**
-
-### 4.1 Setting events
-
-#### 4.1.1 Setting initial and return events
-
-On the funnel page, click "Select Events" to screen initial events and return events. Note that only event properties are selected. If you want to select user properties, you can do so in a global screening.
-
-![](/img/customEvent/retention/LC-2-1.png)
-
-#### 4.1.2 Set "Display at the same time"
-
-The purpose of this feature is to perform an in-depth analysis of the user who triggered the return visit event. For example, you want to count the number of users who completed the login, how many of them completed the payment, and the total amount of the users who completed the payment.
-
-In the following screenshot, the "Display at the same time" function is used to calculate the cumulative sum of the payments made by the paying users.
-
-![](/img/customEvent/retention/LC-2-2-1.png)
-
-The difference from the event analysis template is that the "Display at the same time" function changes the analysis perspective from "sum, median, mean, maximum, minimum, per capita, and de-weighted" to "sum, per capita, phase statistics sum, and phase statistics per capita" for attributes of the numeric type.
-
-And only numeric and boolean attributes can be selected, not time, string, or list attributes, because these types of attributes cannot be "Phase statistics". Using the "Display at the same time" function, we can analyze the attribute values of users who complete the return visit event in the next period of time, such as LTV, N-day payment, cumulative replica damage value, cumulative number of packages purchased per capita, and so on.
-
-
-| Data Type | Analysis Perspective |
-| ----------- | -------------------- |
-| Number | Sum, per capita value, phase cumulative sum, phase cumulative per capita |
-| Boolean | Is true, is false, is null, is not null |
-
-**In addition to the Analysis Perspective of each value type listed in the above table, the default "Analysis Perspective" for any event and any value type are: total number of times, number of triggered users, and number of times per capita.
-"Phase Accumulation" is the core value of the "Simultaneous Display" function. The "Simultaneous Display" function can only do one analysis at most.**
-
-### 4.2 Set Dimension
-
-Time to event is a required dimension for retention analysis because retention is associated with time. In addition to the time dimension, you can choose up to four additional dimensions.
-
-![](/img/customEvent/retention/LC-2-3.png)
-
-### 4.3 Set Display Results
-
-The report of retention analysis is still in the form of a pivot table. Unlike the event analysis report, the dimension of "Event Time" cannot be dragged and dropped to change the order of aggregation, and "Event Time" can only be in the first column.
-
-![](/img/customEvent/retention/LC-2-4.png)
-
-#### 4.3.1 Select the analysis period for retention
-
-![](/img/customEvent/retention/LC-2-5.png)
-
-The default analysis period for retention is 7 days. Click on the drop-down box to select: current day, next day, 7 days, 14 days, 30 days, current week, next week, 4 weeks, 8 weeks, 16 weeks, current month, next month, 3 months, 6 months, 12 months.
-
-In addition to the above time, you can also fill in n days, n weeks, n months by yourself.
-
-If the selected analysis period is too long, for example, 180 days, the calculation will be slow due to the large amount of data, so you can use "Show key dates only".
-
-- The key dates for days are: 1, 7, 14, 21, 30, 60, 90, 120, 150, 180, 360.
-- The key dates for weeks are: 1, 4, 8, 16, 24, 32, 40, 48, 52.
-- The key dates for months are: 1, 3, 6, 12, 24.
-
-Checking "Show key dates only" will save time for searching. When the selected retention period is longer than 90 days, the system will automatically check the "Show key dates only" box.
-
-#### 4.3.2 Retention / Loss
-
-![](/img/customEvent/retention/LC-2-6.png)
-
-The logic of user retention on day n: If b players (assuming the number of a) who triggered the initial event trigger a return visit on day n, the "retention percentage" is b/a.
-The logic of lost users on day n: If b players who triggered the initial event (assuming the number of a players) do not trigger a return event from day 1 to day n (duration), b is the number of "lost users" on day n. The "churn percentage" is b/a.
-
-#### 4.3.3 Display number / percentage
-
-On the right side of the report, you can choose to display all / percentage only / quantity only.
-
-![](/img/customEvent/retention/LC-2-7-2.png)
-
-### 4.4 Save Report
-
-![](/img/customEvent/retention/LC-2-7-3.png)
-
-Save the query results as a report, and then create a dashboard based on the report. A dashboard makes it easy to view retention analysis results.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/sqlide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/sqlide.mdx
deleted file mode 100644
index 3a3148411..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/sqlide.mdx
+++ /dev/null
@@ -1,229 +0,0 @@
----
-title: SQL Query
-sidebar_position: 14
----
-
-## 1. Overview
-
-You can use SQL Query to query all the data in the project freely and meet the needs of personalized data retrieval and analysis.
-
-Click "Analysis" and "SQL Query" to see the SQL page, which consists of "Write Box" and "Tabs", and the tabs consist of "Table Structure", "Query History", "Statement Bookmarks" and "Query Results".
-
-![](/img/customEvent/sql/sql_1.png)
-
-## 2. Applicable Roles and Uses
-
-| Role | Usage |
-| :-------- | :------------------------------------------------- |
-| Administrator / Analyst | Understand the project's current data assets. |
-|Analyst | Freely write SQL queries for all project data to meet individualized fetching and analysis needs that cannot be met in TapDB's inherent analysis model. |
-| Business | Replace dynamic parameters in analyst SQL code to meet ongoing fetching and analysis needs. | Business
-
-## 3. Table Scope and Notes
-
-### 3.1 Table Scope
-
-In the TapDB SQL query function, the range of library tables can be queried as follows.
-
-| Table Type | Library Name | Table Name |
-| :---: | :-------: | :---------------------: |
-| Events table | tapdb | view_{{Project ID}}_events |
-| devices table | tapdb | view_{{Project ID}}_devices |
-| user table | tapdb | view_{{project ID}}_users |
-| user_cluster | tapdb | view_{{item_id}}_cluster |
-| dimension properties table | tapdb_dim | view_{{item ID}}_{{dimension table name}} |
-
-It is recommended to paste the table name into the compose box by using the "Copy Table Name" function in the "Data Table List", as described in section 5.1.2 of this document.
-
-### 3.2 Notes on using user cluster tables
-
-All user cluster data is stored in the same table "view_{{project ID}}_cluster".
-
-![](/img/customEvent/sql/sql_2.png)
-
-The user IDs under this cluster can be obtained by filtering the cluster name and selecting the phase cluster body field.
-
-The subject of the cluster is "User":
-
-```sql
-select
- user_id
-from hive.tapdb.view_{{项目 ID}}_cluster
-where cluster_name = ‘{{cluster_name}}’
-```
-
-The subject of the cluster is "Device":
-
-```sql
-select
- device_id
-from hive.tapdb.view_{{项目 ID}}_cluster
-where cluster_name = ‘{{cluster_name}}’
-```
-
-## 4. Writing and Executing SQL
-
-The SQL statements are written and executed mainly in the statement writing box.
-
-![](/img/customEvent/sql/sql_3.png)
-
-### 4.1 Basic Syntax
-
-TapDB uses the Presto query engine and applies standard SQL syntax. However, only `select` statements and `with` clauses can be used. You can access the [presto documentation](https://trino.io/docs/332/functions.html) for the Presto syntax and how to use the functions.
-
-Field names in the data table are recommended to be enclosed in double quotes `""`, or by default, but if the query field name has special symbols (e.g. `$`, `#`, etc.), then double quotes must be used.
-Strings must be enclosed in single quotes `' '`.
-
-### 4.2 Partitioning and Time Zones
-
-When querying the event table, you must use the partition key `$part_date` for conditional filtering to avoid a full table scan.
-
-! [](/img/customEvent/sql/sql_4.png)
-
-It is recommended to use the following types of partition constraints.
-
-```sql
-"$part_date" = '2021-11-01'
-"$part_date" in ('2021-11-01', '2021-11-02, '2021-11-03')
-"$part_date" between '2021-11-01' and '2021-11-11'
-```
-
-By default, the SQL query function converts and displays the fields of time type according to the East 8 zone, such as `time` in the event table, `activation_time`, `last_login_time`, `first_charge_time`, `last_charge_time` in the equipment table and account table.
-
-If the project is not in Zone 8 East, it can be transformed using the time function.
-
-```sql
-format_datetime("time" at time zone 'America/Chicago', 'yyyy-MM-dd')
-```
-
-`part_date`, `$part_date` are dates in string format after conversion according to project time zone, if there is no need to query hour, minute and second precisely, it is recommended to use "partition" filter.
-
-### 4.3 Dynamic Parameters
-
-The dynamic parameter function can replace the parameter in the query statement, and the subsequent query can meet the new query requirements by simply entering the parameter value below the parameter input box.
-
-The expression rule of dynamic parameter is `${parameter name}`, the parameter name can include English letters, numbers and "_", it should start with English letters. Parameters with the same name are regarded as one parameter, and multiple parameter variables can be created. The input box below corresponds to the dynamic parameters in the order of the first appearance of each parameter, and parameters with the same name only correspond to one parameter input box.
-
-![](/img/customEvent/sql/sql_5.png)
-
-### 4.4 Toolbar
-
-The toolbar is located below the input box and can perform the following operations.
-
-Formatting: formats the query statement.
-
-Copy statement: Copy the query statement from the input box to the clipboard.
-
-Add Bookmark: Save the query statement as a bookmark for subsequent queries or modifications.
-
-![](/img/customEvent/sql/sql_6.png)
-
-### 4.5 Shortcut keys
-
-When the cursor is in the input box, the following shortcut actions can be performed.
-
-Ctrl + Enter: Perform calculation
-
-Ctrl + Shift + F: Format the current query statement
-
-Ctrl + Z: Undo the previous operation
-
-Ctrl + Y: Resume the previous operation
-
-### 4.6 Executing queries
-
-After completing the SQL statement, you can click the "Calculate" button or the shortcut key Ctrl + Enter to launch a data query.
-
-By default, the maximum number of data to be queried is 10000. The system will automatically add "limit 10000" to the query statement to limit the number of rows of query, and the maximum number of rows to be displayed in the front end is 500. All data can be viewed through the download function in "Query History" and "Query Results".
-
-## 5. Tabs
-
-Tabs consist of "Table Structure", "Query History", "Statement Bookmarks" and "Query Results".
-
-![](/img/customEvent/sql/sql_8.png)
-
-### 5.1 Table Structure
-
-The table structure page can view the detailed information of database, data table and table field, which consists of 3 parts from left to right: database list, data table list and table field list.
-
-![](/img/customEvent/sql/sql_9.png)
-
-#### 5.1.1 Database list
-
-You can view the databases under the project: click on the library name and a list of data tables under the library will be displayed on the right side.
-
-![](/img/customEvent/sql/sql_10.png)
-
-#### 5.1.2 List of Data Tables
-
-You can view the data tables under the selected database: click on the table name and the fields under the table will be displayed on the right side.
-
-Click on "Copy Table Name" and the table name will be pasted to the clipboard.
-
-![](/img/customEvent/sql/sql_11.png)
-
-#### 5.1.3 List of Table Fields
-
-The table fields list allows you to view information about all fields of the selected table, including field names, data types, and explanations.
-
-![](/img/customEvent/sql/sql_12.png)
-
-### 5.2 Query History
-
-Query history page: You can view the executed query statements, including the statement completion time, calculation time, query statements and other information. It also supports searching query statements.
-
-![](/img/customEvent/sql/sql_13.png)
-
-Click "Query ID" to jump to the query result.
-
-Click the "Type" button in the upper right corner of the "Query Statement" to replace the statement in the input box.
-
-Click "Download" to download the query result as a csv format file to local, the maximum number of query results displayed on the page is 500, the data exceeding the maximum number can be downloaded to local for further view and analysis, the maximum number of data downloaded is 10000.
-
-![](/img/customEvent/sql/sql_14.png)
-
-### 5.3 Statement bookmarks
-
-You can view the saved statement bookmarks in the statement bookmarks page.
-
-Click "Set", the content in the bookmark will replace the content in the statement input box, and click "Delete" to delete the bookmark.
-
-![](/img/customEvent/sql/sql_15.png)
-
-### 5.4 Query Results
-
-You can view the history query results in the query result page.
-
-Click "Format" to format json, map and other objects in the query results.
-
-Click "Download" to download the query results as a csv file to the local area.
-
-! [](/img/customEvent/sql/sql_16.png)
-
-## 6. Best Practices
-
-### 6.1 Log Export
-
-Export the user table or event table, e.g. export all event logs for the last 7 days of users.
-
-```sql
-select
- *
-from hive.tapdb.view_{{项目 ID}}_events
-where "$part_date" between '2021-11-05' and '2021-11-11'
-```
-
-### 6.2 Data cleaning and extraction
-
-Extract the key information in complex fields, such as url, json, map, e.g. extract the product ID in the last 10 digits of url.
-
-```sql
-select
- substring("#url", -10) as product_id
-from hive.tapdb.view_{{项目 ID}}_events
-where "$part_date" between '2021-11-05' and '2021-11-11'
-```
-
-### 6.3 Personalized Fetching and Analysis
-
-For personalized analysis needs that cannot be met by TapDB's existing analysis models.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/tracking-management.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/tracking-management.mdx
deleted file mode 100644
index c1b9cd094..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/tracking-management.mdx
+++ /dev/null
@@ -1,122 +0,0 @@
----
-title: Burial Point Management
-sidebar_position: 11
----
-
-## 1. Overview
-
-Burial Point Management is a data management function provided during data access testing and daily use, including 2 sub-modules "Buried point information" and "Reported details".
-
-The buried point information page can view the data received in the project for the last 7 days. It is convenient to quickly understand the overall situation of buried site reporting, as well as error reporting information and sampling examples.
-
-The report details page can view the report details of nearly 1000 buried points during the period when the "monitoring switch" is turned on, and view the buried point report log in real time to help users quickly test and accept the buried point development.
-
-## 2. Applicable Roles and Uses
-
-| Role | Usage |
-| -------- | --------------- |
-| Buried point designers (data product managers / analysts) | Testing and acceptance of new buried point requirements development report results, daily understanding of the overall operation of the buried point, timely detection of buried point errors. |
-| Buried developers (front-end development / client-side development / test engineers) | Real-time view buried logs, test buried results, and quickly locate errors in the buried development process (recommended to be enabled in testing projects). |
-
-## 3. Buried information
-
-The buried information consists of two parts: "Buried information home page" and "Error details page".
-
-**Buried information home page**
-
-![埋点信息主页](/img/customEvent/tracking_manager_1.png)
-
-**Error details page:**
-
-![错误详情页](/img/customEvent/tracking_manager_2.png)
-
-### 3.1. Buried Information Home Page
-
-The buried information supports viewing the last 7 days of data received in the project.
-
-The home page of buried information consists of query settings, interval data, and data details area.
-
-![埋点信息主页](/img/customEvent/tracking_manager_3.png)
-
-#### 3.1.1 Query Setting
-
-Query time filter: The query time refers to the time of data reception. Support custom view of the last 7 days of any time period of data reception, the default statistics of today's data.
-
-Property name, display name search: filter the display of property names and display names according to keywords.
-
-![查询设置](/img/customEvent/tracking_manager_4.png)
-
-#### 3.1.2 Interval Data
-
-Displays the number of received, entered, incorrectly entered, and failed entries for all data in the filtered time period.
-
-![区间数据](/img/customEvent/tracking_manager_5.png)
-
-#### 3.1.3 Data Details
-
-Data name: the name of the data reported by the event burial point is "event name"; the name of the data reported by the burial point that sets user attributes is "user attributes"; the data that cannot be identified as the above two categories is "unknown data".
-
-display name: the display name corresponding to the name of the data.
-
-Received: the data received by the system.
-
-Entered: includes correctly entered and incorrectly entered data.
-
-Incorrectly entered: includes data with wrong attribute level such as illegal event attribute name, illegal attribute type, etc. This kind of data can be entered normally and the wrong attribute is set to empty.
-
-Failed entry: data that cannot be entered due to illegal data format or illegal data name.
-
-Error details: Click to enter the error details page to see the error details displayed according to the error reason.
-
-
-![数据详情](/img/customEvent/tracking_manager_6.png)
-
-### 3.2 Error Details Page
-
-The data detail page displays the error entry and entry failure data under each "Event or User" property, and the error information is displayed according to the error cause.
-
-The error details page consists of query settings, interval data, and data details area.
-
-![错误详情页](/img/customEvent/tracking_manager_7.png)
-
-#### 3.2.1 Data details
-
-Number of error items: the number of data items containing the cause of the error.
-
-Error Type, Processing Result: The error type and the processing result of TapDB, so that users can filter the specific error cause.
-
-View Sample: View the details of the reported data that match the cause of the error, and each reported data can be formatted and copied with one click.
-
-![数据详情](/img/customEvent/tracking_manager_8.png)
-
-#### 3.2.2 Calculation Principle of Error Count
-
-Calculation principle of error count
-
-The "error count" is the result of counting the reported data that triggered the error cause, while the "error cause" in buried management occurs at the "attribute" level.
-
-When only one log of an event is reported, and the property name of field 1 is not legal and the property type of field 2 is not legal, the log will trigger two error reasons at the same time, and the number of error entries will be counted under the two types of error reasons, so the sum of the number of error entries in the data details summary may be greater than the number of error logs.
-
-## 4. Report Details {#realtime}
-
-The report details show the last 1000 logs reported during the period when the "monitoring switch" was turned on, and the processing results of their entry.
-
-![上报明细](/img/customEvent/tracking_manager_9.png)
-
-When "monitoring switch" is on, the buried logs received by the system will be displayed in the reporting details in real time, and when it is on for 1 hour, it will be automatically turned off and no more data will be displayed in real time, and any time the switch is turned on again will reset the 1-hour time progress. It is recommended to manually turn on the "monitoring switch" before testing and acceptance of buried development.
-
-Click the Refresh button to refresh the page for the latest data.
-
-Each line of reported log details can be formatted for viewing and copying.
-
-## 5. Using Buried Point Management
-
-### 5.1 Find the buried point error in time
-
-
-Use the buried point information page to keep track of the overall operation of the buried point, and when there is unknown data, or some data is incorrectly entered or failed to be entered, you can locate the error in the error details page to prevent data assets from being lost.
-
-### 5.2. Use the report log to test and accept the buried point requirements
-
-After the buried developers finish the development, the designers can simulate the user's various clicking behaviors in the game, and then check the real-time reporting logs in the "Reporting Log" to check whether the reporting timing and log details are consistent with the buried design plan, so as to test and accept the buried requirements.
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/user-seq.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/user-seq.mdx
deleted file mode 100644
index 213db8b2d..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/user-seq.mdx
+++ /dev/null
@@ -1,55 +0,0 @@
----
-title: User Search
-sidebar_position: 18
----
-
-
-## 1. What Is User Search?
-
-User search is used to find the behavior of users that match a certain type of characteristic or an exact user.
-
-## 2. Usage Scenarios
-
-* Funnel analysis finds users who churn during login or during the newbie tutorial. "User Search" finds the behavior of users before they churn.
-* Review the user's behavior sequence before Crash to identify problem scenarios.
-
-## 3. How To Use User Search?
-
-By analyzing the model if the result of analysis is population (number of triggering accounts, number of triggering devices), then click on the analysis result to display the list of users who match the conditions and the details of these users. You can also use the user properties to filter the matching users. Clicking on a user in the list can get the reported behavior distribution, behavior sequence, user properties, etc. for that user in a certain time period.
-
-### 3.1 User search
-
-Click "User Search" at the top right to open the user search interface.
-
-![](/img/customEvent/user/user_seq_1.png)
-
-Select the subject and set the query criteria to find the matching device or account, or you can search precisely by account or device ID.
-
-![](/img/customEvent/user/user_seq_2.png)
-
-### 3.2 User List
-
-From the analytics model or the user refinement page you can jump to the list of users.
-
-![](/img/customEvent/user/user_seq_3.png)
-
-### 3.3 User behavior sequence
-
-Click on the user ID in the user list to access the user behavior sequence query:
-
-![](/img/customEvent/user/user_seq_4.png)
-
-**Total number of behavioral events**
-
-The bar chart will show the trend of the number of reported incidents in the selected time range. Turn on "Show Incident Distribution" on the right to open the pie chart of behavior distribution.
-
-**Event details**
-
-* The user's behavior sequence is displayed in chronological order.
-* Click the expand button to see all event properties of the event.
-* When the event is expanded and the mouse is hovered over the event property, it can be set as an external property. Once set as an external property, you can view the event properties after the event name without expanding the event.
-
-
-**User Properties**
-
-The right side of the page shows the current user's properties, which can be configured using the "Custom Properties" feature.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/user-tag.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/user-tag.mdx
deleted file mode 100644
index a54a3be21..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/user-tag.mdx
+++ /dev/null
@@ -1,86 +0,0 @@
----
-title: User Label
-sidebar_position: 17
----
-
-## 1. Overview
-
-User labels are like user properties, with certain types of user characteristics as "user labels" and the specific performance of users under the characteristics as "label values". It is used to divide users into different groups, and it is easy to use the labels for dimensional grouping or filtering in various analysis models.
-
-In TapDB, "User" and "Device" are used as query subjects for query, and "User" and "Device" are also used as subjects for creating tags in user labels.
-
-It supports creating user labels by "indicator value", and more label creation methods will be opened in the future.
-
-![概述](/img/customEvent/userTag_1.png)
-
-## 2. Applicable Roles and Uses
-
-
-| Roles | Uses |
-| ---------- | --------------------------------- |
-| Analyst / Business Personnel | Calculate user behavior metrics over time, and group and filter users in the analytics model.|
-
-## 3. Create User Labels
-
-Click "New label" in the upper right corner of the tab list and select "index value label".
-
-![创建用户标签_1](/img/customEvent/userTag_2.png)
-
-The current page is divided into two parts: "basic information" and "Index value configuration".
-
-![创建用户标签_2](/img/customEvent/userTag_3.png)
-
-### 3.1 Basic Information
-
-Fill in "Label display name", "Tag", "Label", "Update method", and "Remark" in order.
-
-![基础信息](/img/customEvent/userTag_4.png)
-
-Tag display name is displayed in the subgroup list, analysis model, and is the basis for business personnel to identify tags.
-
-Tag support "User" or "device", choose which type according to the business scenario.
-
-The Label is the unique identification of the subgroup stored in the system background, and can be named as the parameter name with business meaning for the convenience of data analysts to query the database table directly.
-
-The update method is divided into "manual update" and "automatic update". "Manual update" means that the system will not automatically update user tags after the first calculation is completed, and users need to update them manually; "Automatic update" will update user tags after 0:00 every day, using the previous day as the base.
-
-### 3.2 Index Value Configuration
-
-The aggregated metrics of the user's completed events in the specified time period as the tag value.
-
-
-![指标值配置](/img/customEvent/userTag_5.png)
-
-Users who completed the event will belong to the tag, and users who did not complete the event have no tag value.
-
-Determine the tag value of users through indicators. Based on the method of creating indicators in "Event Analysis", add "days" and "hours" of "events", and also support property filtering and editing formulas.
-
-## 4. Management And Use Of Tags
-
-### 4.1 Manage User Tags
-
-The tabs will be displayed in a list on the user tab, and users can view, edit, delete, update, download, and copy operations on the subgroups.
-
-![管理用户标签](/img/customEvent/userTag_6.png)
-
-The "Number" represents the number of users with values under the tag, and the "Data Update Time" is the time when the tag was last calculated.
-
-### 4.2 User Tag Details
-
-Since the values of the indicator value tags are discrete and numerous values, the display distribution of the indicator values can be set in the tag details for reading purposes, but without changing the indicator values themselves.
-
-![用户标签详情](/img/customEvent/userTag_7.png)
-
-### 4.3 Using User Tags In Analytics Model
-
-As with user properties, user tags can be used as filter conditions.
-
-![在分析模型中使用用户标签_1](/img/customEvent/userTag_8.png)
-
-The user tag can be used as a grouping dimension.
-
-![在分析模型中使用用户标签_2](/img/customEvent/userTag_9.png)
-
-## 5. Best Practices
-
-In " Indicator Value", you can calculate indicators of each user's behavior in a certain time period, such as the number of days logged in, amount paid, etc. These indicator value tags can be used as grouping dimensions in event analysis, attribute analysis to group users for analysis, or as filtering items for further drill-down analysis of users.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/virtual-event.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/virtual-event.mdx
deleted file mode 100644
index 0b10d11dc..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/virtual-event.mdx
+++ /dev/null
@@ -1,69 +0,0 @@
----
-title: Virtual Events
-sidebar_position: 15
----
-
-## 1. Overview
-
-A virtual event can be composed of multiple events with similar business meaning, and any base event triggered is considered to be triggered as the virtual event.
-
-It is also possible to split an event into multiple events by different filtering conditions, and the base event that meets the filtering conditions is triggered as the virtual event.
-
-You can create and manage virtual events in the event management page.
-
-![overview](/img/customEvent/virtualEvent_1.png)
-
-## 2. Applicable Roles and Uses
-
-| Role | Usage |
-| ------------------------- | ------------------------------------ |
-| Buried Event Designer (Data Product Manager / Analyst) | To transform buried events, reduce the complexity of buried solution design, and make up for the shortcomings in buried design and development. |
-| Data analysts (data product managers / analysts / operations) | Combine events with similar business meaning, or split an event to improve the efficiency of daily analysis use. |
-
-## 3. Create Virtual Event
-
-Click "New Event" in the upper right corner of the event management page and select "Virtual Event".
-
-![New Virtual Event](/img/customEvent/virtualEvent_2.png)
-
-### 3.1 Fill in the basic information
-
-Fill in the virtual event name, virtual event display name and description, the event name starts with "#ve@" by default.
-
-![Fill in the basic information](/img/customEvent/virtualEvent_4.png)
-
-### 3.2 Edit the rules for defining virtual events
-
-A virtual event is considered to be triggered when any of its base events is triggered (if the base event is filtered, the filtering condition must be satisfied here).
-
-When a virtual event consists of 2 or more base events, it can be filtered globally, and all base events must satisfy the filtering condition
-
-![编辑虚拟事件的定义规则](/img/customEvent/virtualEvent_3.png)
-
-## 4. Management and use of virtual events
-
-### 4.1 Managing virtual events
-
-You can manage virtual events in the event management page. The maximum number of virtual events that can be created is 300, and they can be edited, copied and deleted.
-
-![Manage virtual events](/img/customEvent/virtualEvent_5.png)
-
-### 4.2 Notes for use in the analysis model
-
-Virtual events are consistent in their use with preconfigured events and custom events. The properties of a virtual event are the intersection of all the properties of the base event.
-
-The number of users triggering virtual events has been "de-duplicated" based on the number of users of the base event. Therefore, in some cases the number of users triggered by virtual events is less than the sum of the number of users triggered by all base events.
-
-## 5. Best Practices
-
-### 5.1 Combine events with similar business meaning into a single event
-
-In the buried design, "big world battle", "copy battle" and "PVP battle" are reported as 3 different events.
-
-Now we need to count all the battle behaviors of users. The three virtual events mentioned above can be used as the base event of "Combat", and users triggering any one of the three combat events can be considered as triggering "Combat" events. This way, all the user's combat behaviors can be counted.
-
-### 5.2 Splitting a single event into multiple events with more specific business meaning
-
-In the buried design, all page views are uniformly reported as "page view" and different pages are distinguished by the event attribute "page type".
-
-In the daily analysis, the analysis demand of home page, store page and recharge page is more frequent, so we can use "page view" as the basic event in the virtual event and filter "page type" to construct 3 virtual events "home page view", "store page view" and "recharge page view", which can realize fast statistics and meet the daily analysis demand.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/virtual.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/virtual.mdx
deleted file mode 100644
index 3971060b9..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/custom-event/virtual.mdx
+++ /dev/null
@@ -1,161 +0,0 @@
----
-title: Virtual Properties
-sidebar_position: 12
----
-
-## 1. Overview
-
-For event properties and user properties that have been reported, you can set virtual properties to map the originally uploaded data to another display or calculated value, so that the initial buried property value is not the same as the display value, and the data can be processed at a later time to improve flexibility.
-
-Virtual attributes are calculated by SQL expressions on the base property field to obtain a new property field.
-
-Virtual event properties can be created and managed in event property management, and virtual user properties can be created and managed in user property management.
-
-## 2. Applicable Roles and Uses
-
-
-| Roles | Usage
-| ------------------------- | ------------------------------------ |
-| Data Product Manager / Analyst | Transformation of buried fields, reduce the complexity of buried solution design, and make up for the defects in buried design and development. |
-| Data analysts (data product managers / analysts / operations) | Self-assisted extraction of some of the low-frequency or a small number of scenarios from the existing buried data, rapid response to analysis needs. |
-
-## 3. New Virtual Attribute
-
-Click "New Event Attribute" or "New User Attribute" in the upper right corner of Event Attribute Management or User Attribute Management, and select "New virtual attribute".
-
-![New Virtual Attribute](/img/customEvent/virtual_1.png)
-
-### 3.1 Fill in the basic information
-
-#### 3.1.1 General Basic Information
-
-Fill in or select the property name, display name, data type, unit, and description, with the property name starting with `#vp@` by default.
-
-
-![General Basic Information](/img/customEvent/virtual_2.png)
-
-#### 3.1.2 虚拟事件属性的独有基础信息
-
-Select "Associated Subjects", different choices correspond to different ranges of base properties (preset properties and custom properties are collectively called base properties), and the application range of this virtual property.
-
-| Associated Subjects | Range of Base Attributes | Range of Virtual Attribute Applications |
-| ---- | -----------------------| ------------- |
-| No Subject | Event Attributes | When using a device or account as the query subject. |
-| Account number | Event properties & Account properties. Account properties | When using account number as the query subject. |
-| Device | Event properties & Device properties. Device Properties | When the device is the subject of the query.
-
-Select "Associated Events" and the different association logic is as follows.
-
-- Auto-identify: associate the concatenation of the events associated with the base event property involved in the SQL code
-
-- All events: associate all events, or automatically associate if new events are added
-
-- Specified events: associate specified events
-
-! [unique to event property](/img/customEvent/virtual_3.png)
-
-#### 3.1.3 Unique base information for virtual user properties
-
-Select "User ID", different choices correspond to different base attribute ranges, and the application range of this virtual property.
-
-| User ID | Base attribute range | Virtual attribute application range |
-| ---- | ------ | --------- |
-| Account | Account Properties | When the account is the subject of the query. |
-| Device | Device Properties | When the device is the subject of the query. |
-
-! [unique base information for account attributes](/img/customEvent/virtual_4.png)
-
-### 3.2 Edit the creation rules of virtual attributes
-
-You can quickly get information about the currently available base properties in the list on the left side by entering SQL expressions in the text box.
-
-![创建规则](/img/customEvent/virtual_5.png)
-
-The SQL expressions of virtual properties use presto syntax, and you can access [presto documentation](https://trino.io/docs/332/functions.html) to get the syntax of presto and how to use the functions.
-
-You can insert code snippets required for common application scenarios through the "Insert Template" function, and create virtual properties easily and quickly by replacing field names and modifying the logic.
-
-### 3.3 Logic Checking
-
-After inputting the content, you can click "Verify" to check the range and SQL syntax of the base property, and the value input box of the property will appear in the "Debug" panel if the verification is successful.
-
-You can check whether the result meets the requirement by checking the result after entering the validation value. If it meets the requirements, you can click "Next" to finish creating the property. If it does not meet the expectations, you can continue to modify the creation rules and repeat the verification.
-
-![logical_check](/img/customEvent/virtual_6.png)
-
-## 4. Management and use of virtual properties
-
-### 4.1 Managing virtual properties
-
-Virtual properties can be managed in the event property management or user property management pages. The maximum number of virtual event attributes and virtual user attributes that can be created is 300, and they can be edited and deleted.
-
-![Manage virtual properties](/img/customEvent/virtual_7.png)
-
-### 4.2 Considerations for use in models
-
-Virtual properties are used in the same way as usual properties, with their calculation logic and filtering conditions determined by their type.
-
-Virtual event properties can be used in the calculation of their associated events, and the association can be customized.
-
-Virtual user properties are used in the same scenarios as normal user properties.
-
-## 5. Best Practices
-
-### 5.1 Fix a field type reporting error in buried development
-
-The user age field "age" was incorrectly reported as text. Before re-release, the user age is now temporarily and urgently needed for analysis.
-
-```sql
-cast("age" as int)
-```
-
-### 5.2 Calculating user lifecycle
-
-Based on the user's "activation_time" and the event "time", we need to calculate the user's lifecycle when the user's action occurs.
-
-```sql
-date_diff('day', date("activation_time"), date("time"))
-```
-
-### 5.3 Unit conversion by arithmetic operations
-
-There is already a payment amount field "amount" in "cents", which needs to be converted to "dollars".
-
-```sql
-"amount" / 100
-```
-
-### 5.4 Identifying separate roles or other subjects that you want to analyze
-
-A user can create different roles on different servers. The fields identifying the roles are generated based on the user identity field "user_id" and the server identity field "server_id".
-
-```sql
-concat("server_id", "user_id")
-```
-
-### 5.5 Intercepting fields to obtain key dimensional information in complex fields
-
-Intercept the month of posting based on the ID "post_id " of the forum post (example: 10086202012310001).
-
-```sql
-substring("post_id", 6, 6)
-```
-
-### 5.6 Sorting pages by functional modules
-
-The buried point records the url address of the user's visit, and now it is necessary to know the situation of the functional module used by the user.
-
-
-```sql
-case
-
- when "url" like '%home%' then '首页'
-
- when "url" like '%store%' then '商城'
-
- when "url" like '%stage%' then '剧情'
-
- else '其他'
-
-end
-```
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/diagnosis.md b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/diagnosis.md
deleted file mode 100644
index 8cb4ead65..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/diagnosis.md
+++ /dev/null
@@ -1,68 +0,0 @@
----
-title: Diagnosis
-sidebar_position: 3
----
-
-
-"Diagnosis" is a TapDB module that generates reports based on data monitored by TapTap's self-developed crash monitoring SDK (Themis) and provides related log information to help developers efficiently locate and resolve problems.
-
-## How to use "Diagnosis"?
-
-The "Diagnosis" feature is available for games that are open to TapPlay, integrated with TapSDK and have Themis enabled.
-Please refer to the [documentation](/sdk/tapdb/sdk/client-side-integration/#themis) for how to adjust the configuration.
-
-
-## What scenarios can "Diagnosis" cover?
-
-- Monitor crashes, flashbacks and errors generated during the game, and obtain user behavior logs.
-
-## What modules are included in "Diagnosis"?
-
- - Crash analysis: Monitor the crashes and flashbacks generated during the game, provide positioning conditions and reports, and can be specific to a particular user's crash information.
-
- - Error analysis: Monitor the error reports and custom logs generated during the game, provide location conditions and reports, and can be specific to a particular user's reported information.
-
- - Label management: upload the label to parse and restore the stack of crashes of APP, and quickly and accurately locate the code location where the user's APP crashed.
-
-![「诊断」包含哪些模块?](/img/customEvent/diagnosis/diagnosis-1.png)
-
-## How does crash analysis and error analysis work?
-
-**The functions of "Crash Analysis" and "Error Analysis" are the same, only the monitoring conditions are different, so they are explained together.**
-
-### Observe the stability of the program through "Collapse overview”
-
-1. Observe the running stability trend of the program in the selected date, and determine whether there is a problem in the selected date by the error reporting rate and the reported trend graph.
-
-![观测程序的运行稳定性](/img/customEvent/diagnosis/diagnosis-2.png)
-
-2. The "High probability crash in" chart quickly locates the scenarios where the problem may exist. For example, if the game is connected to TapDB and TapPlay is open, it will additionally provide the distribution of the reported game in TapTap app version.
-
-![高占比统计](/img/customEvent/diagnosis/diagnosis-3.png)
-
-3. Go to the details of the most reported issues through the "TOP 10 list" to quickly anchor the issue.
-
-![Top 10 问题列表](/img/customEvent/diagnosis/diagnosis-4.png)
-
-### How do I identify the cause of the problem?
-
-1. Filter by "Platform" whether the platform you want to view is Andorid or IOS, Android is selected by default.
-
-2. Filter by "Data" which SDK you want to see the data reported, including TapDB and TapPlay.
-
-3. Set the conditions to locate the people who report errors with specific conditions through the filter, after setting the conditions, the data will be reloaded to filter out the information that matches the conditions.
-
-![通过筛选器设置条件定位特定条件的报错人群](/img/customEvent/diagnosis/diagnosis-5.png)
-
-4. Filtering error messages by filtering conditions, the same error reports from different users will be grouped according to characteristics and merged into the same issue ID.
-
- - a. Click on the issue ID to enter the details page, and you can view the trend and distribution of reported issues of this category.
- - b. Click the report ID, the sidebar will pop up the error details of the specific user. Developers can locate the cause of the problem according to the "error stack", "trace data" and so on.
-
-![筛选出符合条件的报错详细信息](/img/customEvent/diagnosis/diagnosis-6.png)
-
-## How does the "Label Management" work?
-
-"Label Management" is used to manage the symbol table uploaded by developers, providing the ability to upload, filter, delete, etc. The detailed use process can be seen in the figure below, if you have any questions, you can submit a ticket for consultation.
-
-![符号表管理](/img/customEvent/diagnosis/diagnosis-7.png)
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/exchange-rate.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/exchange-rate.mdx
deleted file mode 100644
index a64de1d61..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/exchange-rate.mdx
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Exchange Rate
-sidebar_position: 6
----
-
-import { ExchangeTable } from "/src/docComponents/ExchangeTable";
-
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/subcontinent.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/subcontinent.mdx
deleted file mode 100644
index cae9442d3..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/features/subcontinent.mdx
+++ /dev/null
@@ -1,110 +0,0 @@
----
-title: Subcontinent
-sidebar_position: 4
----
-
-The subcontinents are divided according to the United Nations. The countries or regions included in each subcontinent are shown below:
-
-## Africa
-
-**East Africa**
-
-Burundi Comoros Djibouti Eritrea Ethiopia Kenya Madagascar Malawi Mauritius Mayotte Mozambique Réunion Rwanda Seychelles Islands Somalia South Sudan Tanzania Uganda Zambia Zimbabwe French Southern Territories British Indian Ocean Territory
-
-**Central Africa**
-
-Angola Cameroon Central African Republic Chad Congo Zaire Equatorial Guinea Gabon Sao Tome and Principe
-
-**North Africa**
-
-Algeria Egypt Libyan Arab Jamahiriya Morocco Sudan Tunisia Western Sahara
-
-**South Africa**
-
-Botswana Swaziland Lesotho Namibia South Africa
-
-**West Africa**
-
-Benin Burkina Faso Cape Verde Ivory Coast Gambia Ghana Guinea Guinea-Bissau Liberia Mali Mauritania Niger Nigeria Senegal Sierra Leone St. Helena Togo
-
-## Americas
-
-**Caribbean**
-
-Anguilla Antigua and Barbuda Aruba Barbados Bahamas British Virgin Islands Bonaire, St. Eustatius and Saba Cayman Islands Cuba Curaçao Dominica Dominican Republic Grenada Guadeloupe Haiti Martinique Montserrat Islands Puerto Rico St. Maarten St. Barthélemy St. Kitts and Nevis St. Lucia St. Martin St. Vincent and the Grenadines Trinidad and Tobago Turks and Caicos Islands U.S. Virgin Islands Jamaica
-
-**Central America**
-
-Belize Costa Rica Guatemala El Salvador Honduras Mexico Nicaragua Panama
-
-**North America**
-
-Bermuda Canada Greenland St. Pierre and Miquelon United States
-
-**South America**
-
-Argentina Bolivia Bouvet Island Brazil Chile Colombia Ecuador Falkland Islands French Guiana Guyana Paraguay Peru South Georgia and the South Sandwich Islands Suriname Uruguay Venezuela
-
-## Asia
-
-**Central Asia**
-
-Kazakhstan Kyrgyzstan Tajikistan Turkmenistan Uzbekistan
-
-**East Asia**
-
-China Hong Kong Japan Macau Mongolia Korea, Democratic Republic of Korea Taiwan
-
-**Southeast Asia**
-
-Brunei Cambodia Indonesia Lao People's Democratic Republic Malaysia Myanmar Philippines Singapore Thailand Vietnam Timor-Leste
-
-**South Asia**
-
-Afghanistan Bangladesh Bhutan India Iran (Islamic Republic of) Maldives Nepal Pakistan Sri Lanka
-
-**West Asia**
-
-Armenia Azerbaijan Bahrain Cyprus Georgia Iraq Israel Jordan Kuwait Lebanon Oman
-
-Palestinian Territories Qatar Saudi Arabia Syria Turkey United Arab Emirates Yemen
-
-## Europe
-
-**Eastern Europe**
-
-Belarus Bulgaria Czech Republic Hungary Republic of Moldova Poland Romania Russia Slovak Republic Ukraine
-
-**Northern Europe**
-
-Åland Islands Denmark Estonia Faroe Islands Finland Guernsey Iceland Ireland Isle of Man Jersey Latvia Lithuania Norway Svalbard and Jan Mayen Sweden United Kingdom
-
-**Southern Europe**
-
-Albania Andorra Bosnia and Herzegovina Croatia Gibraltar Greece Italy Kosovo Malta Montenegro The former Yugoslav Republic of Macedonia Portugal San Marino Serbia Slovenia Spain Holy See (Vatican)
-
-**Western Europe**
-
-Austria Belgium France Germany Liechtenstein Luxembourg Monaco Netherlands Switzerland
-
-## Oceania
-
-**Australasia**
-
-Australia Christmas Island Cocos Islands Heard and McDonald Islands New Zealand Norfolk Island
-
-**Melanesia**
-
-Fiji New Caledonia Papua New Guinea Solomon Islands Vanuatu
-
-**Micronesia**
-
-Guam Kiribati Marshall Islands Micronesia Nauru Northern Mariana Islands Palau U.S. Minor Outlying Islands
-
-## Oceania Overseas Territories
-
-Antarctica
-
-**Polynesia**
-
-American Samoa Cook Islands French Polynesia Niue Pitcairn Islands Tokelau Tonga Tuvalu Wallis and Futuna Samoa
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/_category_.json
deleted file mode 100644
index c59a630d3..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/_category_.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "label": "Guides",
- "position": 3
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/client-side-integration.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/client-side-integration.mdx
deleted file mode 100644
index 8a9b8d078..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/client-side-integration.mdx
+++ /dev/null
@@ -1,1646 +0,0 @@
----
-title: Client Integration Guides
-sidebar_label: Client Integration Guides
-sidebar_position: 3
----
-
-import MultiLang from '/src/docComponents/MultiLang';
-import CodeBlock from '@theme/CodeBlock';
-import sdkVersions from '/src/docComponents/sdkVersions';
-import {Conditional} from '/src/docComponents/conditional';
-
-## Introduction
-
-TapSDK provides an API for game developers to collect account data. The system will collect and analyze the account data, and eventually form a data report to help game developers analyze account behavior and optimize the game.
-
-##
-
-Please [download TapSDK](/tap-download) and add the relevant dependencies.
-
-
-
-<>
-
-If you only need to use TapDB alone, you can import only the dependencies `common` and `tapdb`.
-
-For Unity v3.7.1 and higher, you also need to import `com.leancloud.storage`.
-
-
-
-{`"dependencies":{
- "com.taptap.tds.login":"https://github.com/TapTap/TapLogin-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.common":"https://github.com/TapTap/TapCommon-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.bootstrap":"https://github.com/TapTap/TapBootstrap-Unity.git#${sdkVersions.taptap.unity}",
- "com.leancloud.realtime": "https://github.com/leancloud/csharp-sdk-upm.git#realtime-${sdkVersions.leancloud.csharp}",
- "com.leancloud.storage": "https://github.com/leancloud/csharp-sdk-upm.git#storage-${sdkVersions.leancloud.csharp}",
- // Data analysis
- "com.taptap.tds.tapdb": "https://github.com/TapTap/TapDB-Unity.git#${sdkVersions.taptap.unity}",
-}`}
-
->
-
-<>
-
-If you only need to use TapDB alone, you can import only the dependencies `common` and `tapdb`.
-
-
-{`repositories {
- flatDir {
- dirs 'libs'
- }
-}
-dependencies {
- // ...
- implementation (name:'TapBootstrap_${sdkVersions.taptap.android}', ext:'aar') // Required: TapSDK Launcher
- implementation (name:'TapCommon_${sdkVersions.taptap.android}', ext:'aar') // Required: TapSDK base library
- implementation (name:'TapLogin_${sdkVersions.taptap.android}', ext:'aar') // Required: TapTap Login
- implementation 'com.taptap:lc-realtime-android:${sdkVersions.leancloud.java}'
- implementation 'com.taptap:lc-storage-android:${sdkVersions.leancloud.java}'
- implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
- implementation (name:'TapDB_${sdkVersions.taptap.android}', ext:'aar') //Statistics
-}`}
-
->
-
-<>
-
-If you only need to use TapDB alone, you can import only the dependencies `common` and `tapdb`.
-
-
-{`// Login
-TapBootstrapSDK.framework
-TapCommonSDK.framework
-TapLoginSDK.framework
-TapCommonResource.bundle
-TapLoginResource.bundle
-LeanCloudObjc.framework
-//TapDB
-TapDB.framework`}
-
-
-Add: `-ObjC` and `-Wl -ld_classic` to 'Build Settings' >' link '>' Other Linker Flags'.
-The following dependent frameworks or libraries need to be imported for your Xcode project.
-
-Package | Description | Notes
---- | --- | ---
-AdSupport.framework | Used to obtain device advertising logos and track devices
-iAd.framework | Advertising frame | Set to optional
-AdServices.framework | Advertising frame | Set to optional
-AppTrackingTransparency.framework | App tracking framework added in iOS 14 (not required if IDFA tracking is not required beyond iOS 14) | Set to optional
-SystemConfiguration.framework |
-CoreMotion.framework |
-CoreTelephony.framework |
-Security.framework | Used to persist the storage device ID
-libc++.tdb |
-libresolv.tbd |
-libz.tbd |
-libsqlite3.0.tbd |
-
-
->
-
-<>
-
-#### Environmental Requirements
-
-* UE 4.26 or higher
-* iOS 12 or higher
-* Android 5.0 (API level 21) or higher
-* macOS 10.14.0 or higher
-* Windows 7 or higher
-
-**Support Platforms**:iOS / Android / Windows / macOS
-
-#### Install Plugins
-
-* Download [TapSDK UE4](/tap-download), unzip TapSDK-UE4-xxx.zip, and copy the `TapDB` and `TapCommon` folders to the `Plugins` directory.
-* Restart Unreal Editor.
-* Open Edit > Plugins > Projects > TapTap, open the `TapDB` module.
-
-#### Add dependencies
-
-Add the required modules to `Project.Build.cs`:
-
-```cs
-PublicDependencyModuleNames.AddRange(new string[] { "Core",
- "CoreUObject",
- "Engine",
- "Json",
- "InputCore",
- "JsonUtilities",
- "SlateCore",
- "TapCommon",
- "TapDB"
-});
-```
-
->
-
-
-
-:::info
-If you were using a version of TapDB lower than `3.6.3` and the region selected for initialization was International (`IO`), then upgrading the SDK to `3.6.3` and higher will require migrating the data first.Please submit a ticket to contact us to migrate the data.
-:::
-
-
-## Initialize SDK
-
-
-Initialize the SDK and report a device login (`device_login`) event, access to this interface is a pre-requisite for using other interfaces and needs to be called early.
-
-:::info
-Depending on your use case, either of the following initialization options is acceptable.
-:::
-
-### Initialize TapSDK {#tapsdk-init}
-
-Synchronize the initialization of TapDB during TapSDK initialization.
-
-
-
-<>
-
-
-
-```cs
-using TapTap.Bootstrap;
-
-var config = new TapConfig.Builder()
- .ClientID("your_client_id") // (Required) Client ID in the Developer Center.
- .ClientToken("your_client_token") // (Required) Client Token in the Developer Center.
- .ServerURL("https://your_server_url") // (Required) Developer Center > Your Game > Game Services > Configuration > Domain > API
- .RegionType(RegionType.CN) // (Optional) CN for Mainland China; IO for international.
- .TapDBConfig(true, "gameChannel", "gameVersion", true) // TapDB is automatically initialized based on the configuration of TapConfig.
- .ConfigBuilder();
-
-TapBootstrap.Init(config);
-```
-
-
-
-
-
-```cs
-using TapTap.Bootstrap;
-
-var config = new TapConfig.Builder()
- .ClientID("your_client_id") // (Required) Client ID in the Developer Center.
- .ClientToken("your_client_token") // (Required) Client Token in the Developer Center.
- .ServerURL("https://your_server_url") // (Required) Developer Center > Your Game > Game Services > Configuration > Domain > API
- .RegionType(RegionType.IO) // (Optional) CN for Mainland China; IO for international.
- .TapDBConfig(true, "gameChannel", "gameVersion", true) // TapDB is automatically initialized based on the configuration of TapConfig.
-
- .ConfigBuilder();
-
-TapBootstrap.Init(config);
-```
-
-
-
-TapDBConfig descriptions:
-
-```cs
-public Builder TapDBConfig(bool enable, string channel, string gameVersion, bool advertiserIDCollectionEnabled)
-```
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-enable | No | Whether to open TapDB service.
-channel | Yes | Subcontracting channels, length not greater than 256.
-version | Yes | Game version, when empty, automatically obtains the version of the game installation package. The length is not greater than 256.
-advertiserIDCollectionEnabled | No | IDFA switch, please refer to the [Collect Device Fingerprint](/sdk/tapdb/sdk/client-side-integration#idfaios) documentation.
-
->
-
-<>
-
-
-
-```java
-TapDBConfig tapDBConfig = new TapDBConfig();
-tapDBConfig.setEnable(true); // Whether to open TapDB service.
-tapDBConfig.setChannel("gameChannel"); // Subcontracting channels, length not greater than 256.
-tapDBConfig.setGameVersion("1.0.0"); // Game version, when empty, automatically obtains the version of the game installation package. The length is not greater than 256.
-
-TapConfig tapConfig = new TapConfig.Builder()
- .withAppContext(getApplicationContext())
- .withClientId("your_client_id") // (Required) Client ID in the Developer Center.
- .withClientToken("your_client_token") // (Required) Client Token in the Developer Center.
- .withServerUrl("https://your_server_url") // (Required) Developer Center > Your Game > Game Services > Configuration > Domain > API
- .withRegionType(TapRegionType.CN) // TapRegionType.CN: China; TapRegionType.IO: Other countries or regions。
- .withTapDBConfig(tapDBConfig)
- .build();
-TapBootstrap.init(MainActivity.this, tapConfig);
-```
-
-
-
-
-
-
-```java
-TapDBConfig tapDBConfig = new TapDBConfig();
-tapDBConfig.setEnable(true); // Whether to open TapDB service.
-tapDBConfig.setChannel("gameChannel"); // Subcontracting channels, length not greater than 256.
-tapDBConfig.setGameVersion("1.0.0"); // Game version, when empty, automatically obtains the version of the game installation package. The length is not greater than 256.
-
-TapConfig tapConfig = new TapConfig.Builder()
- .withAppContext(getApplicationContext())
- .withClientId("your_client_id") // (Required) Client ID in the Developer Center.
- .withClientToken("your_client_token") // (Required) Client Token in the Developer Center.
- .withServerUrl("https://your_server_url") // (Required) Developer Center > Your Game > Game Services > Configuration > Domain > API
- .withRegionType(TapRegionType.IO) // TapRegionType.CN: China; TapRegionType.IO: Other countries or regions.
- .withTapDBConfig(tapDBConfig)
- .build();
-TapBootstrap.init(MainActivity.this, tapConfig);
-```
-
-
-
->
-
-<>
-
-
-
-```objectivec
-
-TapConfig *config = [TapConfig new];
-config.clientId = @"your_client_id"; // (Required) Client ID in the Developer Center.
-config.clientToken = @"your_client_token"; // (Required) Client Token in the Developer Center.
-config.region = TapSDKRegionTypeCN; // TapRegionType.CN: China; TapRegionType.IO: Other countries or regions.
-config.serverURL = @"https://your_server_url"; // // (Required) Developer Center > Your Game > Game Services > Configuration > Domain > API.
-
-TapDBConfig * dbConfig = [[TapDBConfig alloc]init];
-dbConfig.enable = YES; // Whether to open TapDB service.
-dbConfig.channel=@"taptap"; // Subcontracting channels, length not greater than 256.
-dbConfig.gameVersion=@"1.0.0"; // Game version, when empty, automatically obtains the version of the game installation package. The length is not greater than 256.
-dbConfig.advertiserIDCollectionEnabled=YES; // IDFA switch, please refer to the [Collect Device Fingerprint](/sdk/tapdb/sdk/client-side-integration#idfaios) documentation.
-config.dbConfig = dbConfig;
-
-config.region = TapSDKRegionTypeCN;
-[TapBootstrap initWithConfig:config];
-```
-
-
-
-
-
-```objectivec
-
-TapConfig *config = [TapConfig new];
-config.clientId = @"your_client_id"; // (Required) Client ID in the Developer Center.
-config.clientToken = @"your_client_token"; // (Required) Client Token in the Developer Center.
-config.region = TapSDKRegionTypeIO; // TapRegionType.CN: China; TapRegionType.IO: Other countries or regions.
-config.serverURL = @"https://your_server_url"; // (Required) Developer Center > Your Game > Game Services > Configuration > Domain > API.
-
-TapDBConfig * dbConfig = [[TapDBConfig alloc]init];
-dbConfig.enable = YES; // Whether to open TapDB service.
-dbConfig.channel=@"taptap"; // Subcontracting channels, length not greater than 256.
-dbConfig.gameVersion=@"1.0.0"; // Automatically get the game version When the value is null. The length is not greater than 256.
-dbConfig.advertiserIDCollectionEnabled=YES; // IDFA switch, please refer to the [Collect Device Fingerprint](/sdk/tapdb/sdk/client-side-integration#idfaios) documentation.
-config.dbConfig = dbConfig;
-
-[TapBootstrap initWithConfig:config];
-```
-
-
-
->
-
-<>
-
-Initializing in this way requires importing the `TapBootstrap`, opening the `TapBootstrap`, and adding `TapBootstrap` to the `Project.Build.cs` file.
-
-Import the header file.
-
-```cpp
-#include "TapUEBootstrap.h"
-#include "TapUECommon.h"
-#include "TapUEDB.h"
-```
-
-
-
-```cpp
-FTUConfig Config;
-Config.ClientID = ClientID; // (Required) Client ID in the Developer Center.
-Config.ClientToken = ClientToken; // (Required) Client Token in the Developer.
-Center.Config.ServerURL = ServerURL; // (Required) Developer Center > Your Game > Game Services > Configuration > Domain > API.
-Config.RegionType = ERegionType::CN;
-Config.DBConfig.Enable = true;
-Config.DBConfig.Channel = Channel;
-Config.DBConfig.GameVersion = GameVersion;
-Config.DBConfig.AdvertiserIDCollectionEnabled = AdvertiserIDCollectionEnabled;
-TapUEBootstrap::Init(Config);
-```
-
-
-
-
-
-```cpp
-FTUConfig Config;
-Config.ClientID = ClientID; // (Required) Client ID in the Developer Center.
-Config.ClientToken = ClientToken; // (Required) Client Token in the Developer.
-Center.Config.ServerURL = ServerURL; // (Required) Developer Center > Your Game > Game Services > Configuration > Domain > API.
-Config.RegionType = ERegionType::IO;
-Config.DBConfig.Enable = true;
-Config.DBConfig.Channel = Channel;
-Config.DBConfig.GameVersion = GameVersion;
-Config.DBConfig.AdvertiserIDCollectionEnabled = AdvertiserIDCollectionEnabled;
-TapUEBootstrap::Init(Config);
-```
-
-
-
-#### DBConfig Descriptions
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-Enable | No | Whether to open TapDB service.
-Channel | Yes | Subcontracting channels, length not greater than 256.
-Version | Yes | Automatically get the game version When the value is null. The length is not greater than 256.
-AdvertiserIDCollectionEnabled | No | IDFA switch, please refer to the [Collect Device Fingerprint](/sdk/tapdb/sdk/client-side-integration#idfaios) documentation.
-
->
-
-
-
-### TapDB Only
-
-When only using the TapDB without using the login functionality and without importing the `TapBootstrap` package, TapDB can be initialized as follows:
-
-
-
-<>
-
-
-
-```cs
-public static void Init(string clientId, string channel, string gameVersion, bool isCN)
-
-TapDB.Init("clientId", "taptap", "gameVersion", true);
-```
-
-
-
-
-
-```cs
-public static void Init(string clientId, string channel, string gameVersion, bool isCN)
-
-TapDB.Init("clientId", "taptap", "gameVersion", false);
-```
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-clientId | No | Client ID in the Developer Center.
-channel | Yes | Subcontracting channels.
-version | Yes | Game version, when empty, automatically obtains the version of the game installation package.
-isCN | Yes | true: Chaina,false: Other countries or regions.
-
->
-
-<>
-
-
-
-```java
-public synchronized static void init(final Context context,
- final String clientId,
- final String channel,
- final String gameVersion,
- final boolean isCN)
-
-public synchronized static void init(final Context context,
- final String clientId,
- final String channel,
- final String gameVersion,
- final boolean isCN,
- final JSONObject properties)
-
-TapDB.init(getApplicationContext(), "clientId", "taptap", "gameVersion", true);
-```
-
-
-
-
-
-```java
-public synchronized static void init(final Context context,
- final String clientId,
- final String channel,
- final String gameVersion,
- final boolean isCN)
-
-public synchronized static void init(final Context context,
- final String clientId,
- final String channel,
- final String gameVersion,
- final boolean isCN,
- final JSONObject properties)
-
-TapDB.init(getApplicationContext(), "clientId", "taptap", "gameVersion", false);
-```
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-context | No | The Context object of the current Application or Activity.
-clientId | No | Client ID in the Developer Center.
-channel | Yes | Subcontracting channels, length not greater than 256.
-version | Yes | Game version, when empty, automatically obtains the version of the game installation package.
-isCN | Yes | true: Chaina,false: Other countries or regions.
-properties | Yes | The event property of device login (device_login). It can be passed in with preset attributes to override the default values of the SDK, and it can also be passed in with custom attributes that have been configured in the background.
-
->
-
-<>
-
-
-
-```objectivec
-+ (void)onStartWithClientId:(NSString *)clientId channel:(nullable NSString *)channel version:(nullable NSString *)gameVersion isCN:(BOOL)isCN;
-
-[TapDB onStartWithClientId:@"clientid" channel:@"taptap" version:@"gameVersion" isCN:YES];
-```
-
-
-
-
-
-```objectivec
-+ (void)onStartWithClientId:(NSString *)clientId channel:(nullable NSString *)channel version:(nullable NSString *)gameVersion isCN:(BOOL)isCN;
-
-[TapDB onStartWithClientId:@"clientid" channel:@"taptap" version:@"gameVersion" isCN:NO];
-```
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-clientId | No | Client ID in the Developer Center.
-channel | Yes | Subcontracting channels. Length not greater than 256.
-version | Yes | Game version. When empty, automatically obtains the version of the game installation package (Version in Xcode configuration).
-isCN | Yes | true: Chaina,false: Other countries or regions.
-
->
-
-<>
-
-Import:
-
-```cpp
-#include "TapUEDB.h"
-```
-
-Initialize TapDB:
-
-
-
-```cpp
-FTUDBConfig Config;
-Config.ClientId = TEXT("your client id");
-Config.RegionType = ERegionType::CN;
-Config.Channel = TEXT("your Channel");
-Config.GameVersion = TEXT("GameVersion");
-TapUEDB::Init(Config);
-```
-
-
-
-
-
-```cpp
-FTUDBConfig Config;
-Config.ClientId = TEXT("your client id");
-Config.RegionType = ERegionType::IO;
-Config.Channel = TEXT("your Channel");
-Config.GameVersion = TEXT("GameVersion");
-TapUEDB::Init(Config);
-```
-
-
-
-#### DBConfig
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-ClientId | No | Client ID in the Developer Center.
-RegionType | No | Developer Center > Application Configuration > Applicable Region.
-Channel | Yes | Subcontracting channels. Length not greater than 256.
-GameVersion | Yes | Game version, when empty, automatically obtains the version of the game installation package.
-
->
-
-
-
-## User Setting
-
-### Set User ID
-
-This API records an account when it is called to log in an account. After calling, a user login (user_login) event will be reported, and the "has_user" attribute of the device will be set to true. Before restarting the app or calling clear User, the reported events will all have the account ID.
-
-
-
-<>
-
-```cs
-public static void SetUser(string userId)
-
-TapDB.SetUser("userId");
-```
-
-Parameter | Can be null | Descriptions
-| --- | --- | --- |
-| userId | No | The unique string of the account, the string length is not greater than 256, and can only contain numbers, capital and lower case letters, underscores (_), and dashes (-). Developers need to ensure that the userId of different accounts are not the same. |
-
->
-
-<>
-
-```java
-public static void setUser(final String userId)
-
-public static void setUser(final String userId, final JSONObject properties)
-
-TapDB.setUser("userId");
-
-// Passing event properties simultaneously
-JSONObject properties = new JSONObject();
-properties.put("currentPoints", 10);
-TapDB.setUser("userId", properties);
-```
-
-Parameter | Can be null | Descriptions
-| --- | --- | --- |
-| userId | No | The unique string of the account, the string length is not greater than 256, and can only contain numbers, capital and lower case letters, underscores (_), and dashes (-). Developers need to ensure that the userId of different accounts are not the same. |
-| properties | Yes | Event properties for user login (`user_login`).|
-
->
-
-<>
-
-```objectivec
-+ (void)setUser:(NSString *)userId;
-
-+ (void)setUser:(NSString *)userId properties:(nullable NSDictionary *)properties;
-
-[TapDB setUser:@"userId"];
-
-// Passing event properties simultaneously
-[TapDB setUser:@"userId" properties:@{@"#currentPoints":@10}];
-```
-
-Parameter | Can be null | Descriptions
-| --- | --- | --- |
-| userId | No | The unique string of the account, the string length is not greater than 256, and can only contain numbers, capital and lower case letters, underscores (_), and dashes (-). Developers need to ensure that the userId of different accounts are not the same. |
-| properties | Yes | Event properties for user login (`user_login`).|
-
->
-
-<>
-
-```cpp
-FString UserId = TEXT("userId");
-FString LoginType = TUDBType::LoginType::TapTap;
-TapUEDB::SetUserWithLoginType(UserId, LoginType);
-```
-
-Parameter | Can be null | Descriptions
-| --- | --- | --- |
-| userId | No | The unique string of the account, the string length is not greater than 256, and can only contain numbers, capital and lower case letters, underscores (_), and dashes (-). Developers need to ensure that the userId of different accounts are not the same. |
-| LoginType | Yes | User login method, refer to `TUDBType::LoginType`. |
-
->
-
-
-
-
-### Clear User ID
-
-Once the user logout, you can call `clearUser` to clear the account ID saved in the current SDK, subsequent events reported will not have the account ID, and no events will be reported by calling this interface.
-
-
-
-```cs
-public static void ClearUser()
-
-TapDB.ClearUser();
-```
-
-```java
-public static void clearUser()
-
-TapDB.clearUser();
-```
-
-```objectivec
-+ (void)clearUser;
-
-[TapDB clearUser];
-```
-
-```cpp
-TapUEDB::ClearUser();
-```
-
-
-
-### Set User Name
-
-Once the user logs in, this interface can be called to set the user name, which will update the user name property (`user_name`).
-
-
-
-```cs
-public static void SetName(string name)
-
-TapDB.SetName("Tarara");
-```
-
-```java
-public static void setName(final String name)
-
-TapDB.setName("Tarara");
-```
-
-```objectivec
-+ (void)setName:(NSString *)name;
-
-[TapDB setName:@"Tarara"];
-```
-
-```cpp
-FString Name = TEXT("Tarara"); // 用户游戏昵称
-TapUEDB::SetName(Name);
-```
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-name | No | User name. Length greater than 0 and less than or equal to 256.
-
-
-### Set User Level
-
-Once the user logs in, this interface can be called to set the user level, which will update the user level property ( `user_name` ).
-
-
-
-```cs
-public static void SetLevel(int level)
-
-TapDB.SetLevel(5);
-```
-
-```java
-public static void setLevel(final int level)
-
-TapDB.setLevel(5);
-```
-
-```objectivec
-+ (void)setLevel:(NSInteger)level;
-
-[TapDB setLevel:5];
-```
-
-```cpp
-int32 Level = 5;
-TapUEDB::SetLevel(Level);
-```
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-level | No | User level.
-
-### Set User Server
-
-Once the user logs in, this interface can be called to set the user server, which will update the user server properties (`first_server` and `current_server`).
-
-
-
-```cs
-public static void SetServer(string server)
-
-TapDB.SetServer("1 Server");
-```
-
-```java
-public static void setServer(final String server)
-
-TapDB.setServer("Server 1");
-```
-
-```objectivec
-+ (void)setServer:(NSString *)server;
-
-[TapDB setServer:@"Server 1"];
-```
-
-```cpp
-FString Server = TEXT("Server 1");
-TapUEDB::SetServer(Server);
-```
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-server | No | User server.
-
-
-## Report Charge
-
-This interface can be called to report charge information after a user has made a charge.
-After the call, the `charge` event will be reported and the incoming parameters will be used as properties of the event.
-
-
-
-```cs
-public static void OnCharge(string orderId, string product, long amount, string currencyType, string payment)
-
-public static void OnCharge(string orderId, string product, long amount, string currencyType, string payment,
- string properties)
-
-TapDB.OnCharge("0xueiEns", "game name", "100", "CNY", "wechat", "{\"on_sell\":true}");
-```
-
-```java
-public static void onCharge(final String orderId, final String product, final long amount,
- final String currencyType, final String payment)
-
-public static void onCharge(final String orderId, final String product, final long amount,
- final String currencyType, final String payment, final JSONObject properties)
-
-JSONObject info = new JSONObject();
-info.put("on_sell": true);
-TapDB.onCharge("0xueiEns", "game name", "100", "CNY", "wechat", info);
-```
-
-```objectivec
-+ (void)onChargeSuccess:(nullable NSString *)orderId product:(nullable NSString *)product amount:(NSInteger)amount currencyType:(nullable NSString *)currencyType payment:(nullable NSString *)payment;
-
-+ (void)onChargeSuccess:(nullable NSString *)orderId product:(nullable NSString *)product amount:(NSInteger)amount currencyType:(nullable NSString *)currencyType payment:(nullable NSString *)payment properties:(nullable NSDictionary *)properties;
-
-[TapDB onChargeSuccess:@"0xueiEns" product:@"game name" amount:100 currencyType:@"CNY" payment:@"wechat", properties:@{@"on_sell":YES}];
-```
-
-```cpp
-TapUEDB::OnCharge(OrderId, Product, Amount, CurrencyType, Payment, Properties);
-```
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-orderId | No | Order ID
-product | Yes | Product name
-amount | No | Charge amount
-currencyType | Yes | Currency type, following ISO 4217 standard. Reference: CNY; USD; EUR
-payment | Yes | Payment method
-properties | Yes | The `charge` event properties
-
-
-**Tips:It is recommended to use the server-side recharge statistics interface under conditions that permit it. Please refer to the [server-side integration documentation](/sdk/tapdb/sdk/server-side-integration#充值记录).**
-
-## Custom Events
-
-### Report Events
-
-This interface can be used to report events after the SDK has been initialized:
-
-
-
-```cs
-public static void TrackEvent(string eventName, string properties)
-
-TapDB.TrackEvent("eventName", "{\"weapon\":\"axe\"}");
-```
-
-```java
-public static void trackEvent(final String eventName, final JSONObject properties)
-
-JSONObject properties = new JSONObject();
-properties.put("#weapon", "axe");
-properties.put("#level", 10);
-properties.put("#map", "atrium");
-TapDB.trackEvent("#battle", properties);
-```
-
-```objectivec
-+ (void)trackEvent:(NSString *)eventName properties:(NSDictionary *)properties;
-
-NSDictionary* dic = @{@"aaa":@"xxx",@"bbb":@"yyy"};
-[TapDB trackEvent:@"testEvent2" properties:dic];
-```
-
-```cpp
-TapUEDB::TrackEvent(EventName, Properties);
-```
-
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-eventName | No | Event name
-properties | Yes | Event properties
-
-
-**Tips:**
-
-* The eventName supports reporting of preset events and custom events, where custom events should start with `#`.
-* The key of the event property supports NSString type.
-* The value of the event property supports NSString (maximum length 256) and NSNumber (range of values [-9E15, 9E15]) types.
-* The event property supports reporting of preset properties and custom properties, where custom properties should start with `#`.
-* When passing in preset properties for the event properties, the preset properties collected by default by the SDK will be overwritten.
-
-
-### Sets Generic Event Properties
-
-For properties that need to be carried for all events, it is recommended to use a generic event property implementation.
-
-#### Add Static Generic Event Properties
-
-
-
-```cs
-public static void RegisterStaticProperties(string staticProperties)
-
-//When the static generic event property `#current_channel` is set to `TapDB`, using event reporting is equivalent to adding a `#current_channel` to the event property.
-string properties = "{\"#current_channel\":\"TapDB\"}";
-TapDB.RegisterStaticProperties(properties);
-```
-
-```java
-public static void registerStaticProperties(final JSONObject staticProperties)
-
-//When the static generic event property `#current_channel` is set to `TapDB`, using event reporting is equivalent to adding a `#current_channel` to the event property.
-JSONObject commonProperties = new JSONObject();
-commonProperties.put("#current_channel", "TapDB");
-TapDB.registerStaticProperties(commonProperties);
-```
-
-```objectivec
-+ (void)registerStaticProperties:(NSDictionary *)staticProperties;
-
-// When the static generic event property `#current_channel` is set to `TapDB`, using event reporting is equivalent to adding a `#current_channel` to the event property.
-[TapDB registerStaticProperties:@{@"#current_channel":@"TapDB"}];
-```
-
-```cpp
-TapUEDB::RegisterStaticProperties(Properties);
-```
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-staticProperties | No | Static generic event property dictionary
-
-
-#### Delete A Single Static Generic Event Property
-
-
-
-```cs
-public static void UnregisterStaticProperty(string propertyName)
-
-TapDB.UnregisterStaticProperty("#current_channel");
-```
-
-```java
-public static void unregisterStaticProperty(string propertyName)
-
-TapDB.unregisterStaticProperty("#current_channel");
-```
-
-```objectivec
-+ (void)unregisterStaticProperty:(NSString *)propertyName;
-
-[TapDB unregisterStaticProperty:@"#current_channel"];
-```
-
-```cpp
-TapUEDB::UnregisterStaticProperty(Key);
-```
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-propertyName | No | Static generic event property name
-
-
-#### Delete All Static Generic Event Properties
-
-
-
-```cs
-public static void ClearStaticProperties()
-
-TapDB.ClearStaticProperties();
-```
-
-```java
-public static void clearStaticProperties()
-
-TapDB.clearStaticProperties();
-```
-
-```objectivec
-+ (void)clearStaticProperties;
-
-[TapDB clearStaticProperties];
-```
-
-```cpp
-TapUEDB::ClearStaticProperties();
-```
-
-
-
-#### Register Dynamic Generic Event Properties
-
-For generic event properties that may change at any time. You can register a dynamic generic event property callback to add the calculated property to this reported event property.
-
-
-
-```cs
-public static void RegisterDynamicProperties(IDynamicProperties properties)
-
-// All subsequent events will carry the #currentLevel property, which contains the value of level at the time the event was reported.
-public class TapDBDynamicPropertiesImpl : IDynamicProperties
-{
- public Dictionary GetDynamicProperties()
- {
- Dictionary dic = new Dictionary();
- dic["#currentLevel"] = level;
- return dic;
- }
-}
-TapDB.RegisterDynamicProperties(new TapDBDynamicPropertiesImpl());
-```
-
-```java
-public static void registerDynamicProperties(
- final TapDBDataDynamicProperties dynamicProperties)
-
-// All subsequent events will carry the #currentLevel property, which contains the value of level at the time the event was reported.
-TapDB.registerDynamicProperties(
- () -> {
- JSONObject properties = new JSONObject();
- // getCurrentLevel 在这里仅作为案例,表示用户任何的自有逻辑实现
- long level = getCurrentLevel();
- properties.put("#currentLevel", level);
- return properties;
- }
-);
-```
-
-```objectivec
-+ (void)registerDynamicProperties:(NSDictionary* (^)(void))dynamicPropertiesCaculator;
-
-// All subsequent events will carry the #currentLevel property, which contains the value of level at the time the event was reported.
-[TapDB registerDynamicProperties:^NSDictionary *_Nonnull {
- return @{
- @"#currentLevel": level
- };
- }];
-```
-
-```cpp
-TapUEDB::RegisterDynamicProperties(PropertiesBlock);
-```
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-dynamicProperties | No | Dynamic generic event properties compute callbacks
-
-**Tips:**
-
-* Using the same property name in the reported event or generic property will result in property overwriting. The priority overwriting is from highest to lowest: event property, dynamic generic event property, static generic event property, and preset property. For example, an event property set in `trackEvent` will override a dynamic generic event property, a static generic event property, or a preconfigured property with the same name.
-
-
-## Modify User Attributes
-
-TapDB supports two kinds of objects: devices and accounts, and you can manipulate the properties of these two users through the following interfaces.
-
-### Modify Device Properties
-
-#### Device Property Initialization
-
-This interface can be used to make only the first value valid. Only if the current value is null, the assignment will take effect, if the current value is not null, the assignment will be ignored.
-
-
-
-```cs
-public static void DeviceInitialize(string properties)
-
-string properties = "{\"firstActiveServer\":\"server1\"}";
-TapDB.DeviceInitialize(properties);
-// The value of "#firstActiveServer" in the device table is "server1".
-
-string properties = "{\"firstActiveServer\":\"server2\"}";
-TapDB.DeviceInitialize(properties);
-// The value of "#firstActiveServer" in the device table is still "server1".
-```
-
-```java
-public static void deviceInitialize(final JSONObject properties)
-
-JSONObject properties = new JSONObject();
-properties.put("firstActiveServer", "server1");
-TapDB.deviceInitialize(properties);
-// The value of "#firstActiveServer" in the device table is "server1".
-
-properties.put("firstActiveServer", "server2");
-TapDB.deviceInitialize(properties);
-// The value of "#firstActiveServer" in the device table is still "server1".
-```
-
-```objectivec
-+ (void)deviceInitialize:(NSDictionary *)properties;
-
-[TapDB deviceInitialize:@{@"firstActiveServer":@"server1"}];
-// The value of "#firstActiveServer" in the device table is "server1".
-
-[TapDB deviceInitialize:@{@"firstActiveServer":@"server2"}];
-// The value of "#firstActiveServer" in the device table is still "server1".
-```
-
-```cpp
-TapUEDB::DeviceInitialize(Properties);
-```
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-properties | No | Properties Dictionary
-
-
-#### Device Property Updates
-
-For regular device properties, this interface can be used to assign values, and the new property values will directly overwrite the old ones.
-
-
-
-```cs
-public static void DeviceUpdate(string properties)
-
-string properties = "{\"currentPoints\":10}";
-TapDB.DeviceUpdate(properties);
-// "currentPoints" in the device table is 10.
-
-properties = "{\"currentPoints\":42}";
-TapDB.DeviceUpdate(properties);
-// "currentPoints" in the device table is 42.
-```
-
-```java
-public static void deviceUpdate(final JSONObject properties)
-
-JSONObject properties = new JSONObject();
-properties.put("currentPoints", 10);
-TapDB.deviceUpdate(properties);
-// "currentPoints" in the device table is 10.
-
-properties.put("currentPoints", 42);
-TapDB.deviceUpdate(properties);
-// "currentPoints" in the device table is 42.
-```
-
-```objectivec
-+ (void)deviceUpdate:(NSDictionary *)properties;
-
-[TapDB deviceUpdate:@{@"currentPoints":@10}];
-// "currentPoints" in the device table is 10.
-
-[TapDB deviceUpdate:@{@"currentPoints":@42}];
-// "currentPoints" in the device table is 42.
-```
-
-```cpp
-TapUEDB::DeviceUpdate(Properties);
-```
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-properties | No | Properties dictionary
-
-
-#### Device Property Accumulation
-
-Properties of numeric type can use this interface for accumulation operation, after calling TapDB will accumulate the original property value and save the result value.
-
-
-
-<>
-
-```cs
-public static void DeviceAdd(string properties)
-
-string properties = "{\"totalPoints\":10}";
-TapDB.DeviceAdd(properties);
-// "totalPoints" in the device table is 10.
-
-
-properties = "{\"totalPoints\":-2}";
-TapDB.DeviceAdd(properties);
-// "totalPoints" in the device table is 8.
-```
-
->
-
-<>
-
-```java
-public static void deviceAdd(final JSONObject properties)
-
-JSONObject properties = new JSONObject();
-properties.put("totalPoints", 10);
-TapDB.deviceAdd(properties);
-// "totalPoints" in the device table is 10.
-
-properties.put("totalPoints", -2);
-TapDB.deviceAdd(properties);
-// "totalPoints" in the device table is 8.
-```
-
->
-
-<>
-
-```objectivec
-+ (void)deviceAdd:(NSDictionary *)properties;
-
-[TapDB deviceAdd:@{@"totalPoints":@10}];
-// "totalPoints" in the device table is 10.
-
-[TapDB deviceAdd:@{@"totalPoints":@(-2)}];
-// 此时设备表的 "totalPoints" 字段值为 8
-```
-
->
-
-<>
-
-```cpp
-TapUEDB::DeviceAdd(Properties);
-```
-
->
-
-
-
-Parameter | Can be null | Descriptions
---- | --- | ---
-properties | No | Property dictionary, only supports numeric types
-
-In the above code example, the property value is an integer.
-The accumulation operation also supports floating point numbers, but there is a precision problem with floating point summation, so developers need to pay attention to it.
-
-### Modify User Properties
-
-#### User Property Initialization
-
-Use the same method as "Device Property Initialization" operations.
-
-
-
-```cs
-public static void UserInitialize(string properties)
-
-TapDB.UserInitialize(properties);
-```
-
-```java
-public static void userInitialize(final JSONObject properties)
-
-TapDB.userInitialize(properties);
-```
-
-```objectivec
-+ (void)userInitialize:(NSDictionary *)properties;
-
-[TapDB userInitialize:@{@"firstActiveServer":@"server1"}];
-```
-
-```cpp
-TapUEDB::UserInitialize(Properties);
-```
-
-
-
-
-#### User Property Updates
-
-Use the same method as "Device Property Updates" operations.
-
-
-
-```cs
-public static void UserUpdate(string properties)
-
-TapDB.UserUpdate(properties);
-```
-
-```java
-public static void userUpdate(final JSONObject properties)
-
-TapDB.userUpdate(properties);
-```
-
-```objectivec
-+ (void)userUpdate:(NSDictionary *)properties;
-
-[TapDB userUpdate:@{@"currentPoints":@10}];
-```
-
-```cpp
-TapUEDB::UserUpdate(Properties);
-```
-
-
-
-#### User Property Accumulation
-
-Use the same method as "Device Property Accumulation" operations.
-
-
-
-```cs
-public static void UserAdd(string properties)
-
-TapDB.UserAdd(properties);
-```
-
-```java
-public static void userAdd(final JSONObject properties)
-
-TapDB.userAdd(properties);
-```
-
-```objectivec
-+ (void)userAdd:(NSDictionary *)properties;
-
-[TapDB userAdd:@{@"totalPoints":@10}];
-```
-
-```cpp
-TapUEDB::UserAdd(Properties);
-```
-
-
-
-
-## Collecting Device Fingerprints
-
-Allows the SDK to capture device fingerprints to aid in data analysis, ad attribution and make statistical results more accurate.
-
-:::info
-Please initialize the SDK after the operation such as permission application and setting IDFA switch to ensure the device fingerprint can be reported properly.
-:::
-
-### OAID(Android)
-
-> Note: SDK version 3.15.0 and above support OAID version 1.0.5 ~ 1.2.1; 3.14.0 and below support OAID version 1.0.5 ~ 1.0.25.
-
-
-TapDB SDK will carry this parameter (key is `device_id4`) in sending related events when the application accesses OAID third-party library. The supported versions of this third-party library are 1.0.5 ~ 1.2.1, because different versions change a lot, so the instructions for accessing different versions are as follows.
-
-For 1.0.5 ~ 1.0.25 no additional configuration is needed, just add the dependency of the corresponding third-party library to the application.
-
-
-For 1.0.26 ~ 1.2.1, in addition to adding the corresponding third-party libraries, you need to add the following processing.
-
-#### 1. Set certificate information and profile
-
-The certificate is a "cert.pem" file applied through the Mobile Security Alliance mailbox , which corresponds to the package name. Two types of settings are supported.
-
-1. Copy the `cert.pem` file to the application `assets` directory, and note that the file name should be set to `packageName.cert.pem`, `packageName` is the current application package name.
-2. Set the contents of the certificate file via the SDK interface `setOAIDCert`.
-
-One of the two options is sufficient, and when both are used, the certificate information set through the interface is preferred.
-
-The configuration file is' supplierconfig.json '. The application needs to change the contents corresponding to the internal appid to the application ID of the app market, and the other parts do not need to be modified.
-
-#### 2. Load the corresponding library file in the application project
-
-The library file names for different versions of OAID third-party libraries are as follows:
-
-| Versions | Libraries Name|
-| ---- | ---- |
-| 1.0.30 ~ 1.2.1 | msaoaidsec |
-| 1.0.29 | nllvm1632808251147706677 |
-| 1.0.27 | nllvm1630571663641560568 |
-| 1.0.26 | nllvm1623827671 |
-
-In the 'onCreate' method of the custom 'Application' class in the Android project, add the code to load third-party libraries, for example, when the application integrates OAID version 1.2.1 as follows: The library file names of different versions of OAID third-party libraries are as follows.
-
-```java
-System.loadLibrary("msaoaidsec");
-```
-
-#### FAQ
-
-When the OAID library has been integrated in the project but the device OAID information is still not found when reporting, check the following items:
-
-1. Whether the device time is normal.
-2. For version 1.0.26 and above, does the package name of the certificate correspond to the current package name.
-3. For version 1.0.26 and above, whether the library file is loaded and whether the library file name is the same as the version.
-4. The application in Android 12 reports an error as: `java.lang.UnsatisfiedLinkError`, and the application minSdkVersion is greater than or equal to 23, it is recommended to add in the application tag of AndroidManifest.xml: `android:extractNativeLibs="true"`.
-
-### IMEI(Android)
-
-After adding the following item in 'AndroidManifest.xml' and the user agrees to the permission application, the SDK will automatically collect the Android IMEI.
-
-
-
-<>
-
-```xml
-
-```
-
->
-
-<>
-
-```xml
-
-```
->
-
-<>
-
-```xml
-Not available for iOS platform
-```
-
-```cpp
-//UE4 SDK does not provide this method
-```
-
->
-
-
-
-### IDFA(iOS)
-
-
- For `iOS14.5` and above, getting IDFA requires a popup window with user confirmation. SDK does not get IDFA by default, you can call the interface to enable IDFA fetching.
-
-
-
-<>
-
-Please make sure the permission request description text is added to `info.plist`, the SDK will automatically pop up the permission request window during initialization.
-
-```
-NSUserTrackingUsageDescription
-This item will be used to suggest personalized ads (or other descriptions) to you
-```
-
-If you are using TapSDK initialization, please pass `true` to `advertiserIDCollectionEnabled` in `TapDBConfig` to turn on the IDFA collection switch.
-
-If you are using TapDB SDK alone, please call the following interface to enable the IDFA acquisition switch:
-
-```cs
-TapDB.AdvertiserIDCollectionEnabled(true);
-```
-
->
-
-<>
-
-```java
-// Not available for Android platform
-```
-
->
-
-<>
-
-Please make sure the permission request description text is added to `info.plist`, the SDK will automatically pop up the permission request window during initialization.
-
-```
-NSUserTrackingUsageDescription
-This item will be used to suggest personalized ads (or other descriptions) to you
-```
-
-If you are using TapSDK initialization, please pass `YES` in `advertiserIDCollectionEnabled` in `TapDBConfig` to turn on the IDFA collection switch.
-
-If you are using TapDB SDK alone, please call the following interface to enable the IDFA acquisition switch:
-
-```objective-c
-[TapDB setAdvertiserIDCollectionEnabled:YES];
-```
-
->
-
-<>
-
-iOS exclusive method
-
-```cpp
-TapUEDB::AdvertiserIDCollectionEnabled(true);
-```
-
->
-
-
-
-
-## Diagnosis {#themis}
-
-:::info
-TapSDK 3.14.0 and above can access this feature.
-
-The UE4 SDK does not support diagnostic access at this time.
-:::
-
-### Add Dependencies
-
-
-
-<>
-
-The SDK can be **imported via Unity Package Manager or imported manually**, either way.
-
-If you choose UPM import, you can add to the project's `Packages/manifest.json` file.
-
-
-{`"dependencies":{
- "com.taptap.tds.common":"https://github.com/TapTap/TapCommon-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.tapdb": "https://github.com/TapTap/TapDB-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.themis": "https://github.com/taptap/TapThemis-Unity.git#3.2.3-3",
-}`}
-
-
-If you choose to import manually:
-
-* Find the TapSDK Unity download address on the [download page](/tap-download), download TapSDK-UnityPackage.zip and unzip it, then import the `TapTap_Common`, `TapTap_TapDB` and `TapTap_Themis` modules.
-
->
-
-<>
-
-```cs
-repositories {
- flatDir {
- dirs 'libs'
- }
-}
-dependencies {
- ...
- implementation (name:'THEMIS-release2.6.7', ext:'aar')
-}
-```
-
->
-
-<>
-
-```cs
-// TapDB SDK is called automatically, no additional integration required.
-```
-
->
-
-
-
-### Namespace
-
-
-
-```cs
-using TapTap.TapDB;
-```
-
-```cs
-// TapDB SDK is called automatically, no additional integration required.
-```
-
-```cs
-// TapDB SDK is called automatically, no additional integration required.
-```
-
-
-
-### Interface Description
-
-#### Set the log reporting level
-
-
-
-```cs
-TapDB.ConfigAutoReportLogLevel(LogSeverity.LogError);
-```
-
-```cs
-// not supported
-```
-
-```cs
-// not supported
-```
-
-
-
-The default level is `LogError`, which will be reported automatically when the application log level is higher than the set level.
-
-#### Set whether to exit on exception
-
-
-
-```cs
-TapDB.ConfigAutoQuitApplication(true);
-```
-
-```cs
-// not supported
-```
-
-```cs
-// not supported
-```
-
-
-
-Set whether to exit automatically when an uncaught exception occurs.
-
-#### Register Log Callback
-
-
-
-```cs
-TapDB.RegisterLogCallback(logcallback);
-public void logcallback(string condition, string statckTrace, LogType type ){
-
-}
-```
-
-```cs
-// not supported
-```
-
-```cs
-// not supported
-```
-
-
-
-Register the application log Callback. Callback processing is invoked when the application outputs logs.
-
-#### Remove Log Listening
-
-
-
-```cs
-TapDB.UnRegisterLogCallback(logcallback);
-```
-
-```cs
-// not supported
-```
-
-```cs
-// not supported
-```
-
-
-
-#### Reporting Exception
-
-
-
-```cs
-TapDB.ReportException(new Exception("crash test from unity"),"crashMessage desc");
-```
-
-```cs
-// not supported
-```
-
-```cs
-// not supported
-```
-
-
-
-Proactive reporting an exception.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/data-spec.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/data-spec.mdx
deleted file mode 100644
index 7d71b9701..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/data-spec.mdx
+++ /dev/null
@@ -1,204 +0,0 @@
----
-title: Data Specification
-sidebar_position: 2
----
-
-## Device
-
-### Device ID
-
-When the SDK is initialized, it generates a unique ID for the terminal, which we call the device ID.
-
-#### ID Generation Rules
-
-| SDK | ID generation rules |
-| --- | --- |
-| Android | Try to get the locally saved device ID, Android ID in turn, if neither of them can get the correct result, then randomly generate UUID, the last obtained or generated device ID will be saved in local storage. |
-| iOS | Try to get the local keychain saved device ID, if it fails, random UUID will be generated, and the last obtained or generated device ID will be saved in local storage.|
-
-#### Device Property
-
-Device properties are constant values and up-to-date states. The behavior of these users can be analyzed by correlating a characteristic of the device with an event.
-
-#### FAQ
-
-**Will the device ID change?**
-
-Android:The device ID is constant when the Android ID can be obtained. (Refer to [Google documentation](https://developer.android.com/training/articles/user-data-ids)). If the Android ID is not available, the device ID may change as you reinstall the app or reset the Google Ads ID.
-
-
-iOS:The generated device ID is stored in the device's keychain, which ensures that the device ID is kept stable without resetting the system.
-
-## User
-
-### User ID
-
-User ID is a registered user ID. When a user registers or logs in, you can set the user's unique ID in your system directly or encrypted into the SDK, and this ID is the user's unique ID across platforms.
-
-#### User property
-
-User properties is constant values and up-to-date status. propertys or characteristics can be associated with events to analyze the behavior of these users.
-
-## Event
-
-[What is the event](/sdk/tapdb/sdk/user-event-model)
-
-### Preset Events
-
-| Event Name | Event | Description |
-| --- | --- | --- |
-| device_login | App Launching | This event is reported when the SDK initialization interface is called, and the first time a device ID is reported, a record is generated in the device table. |
-| user_login | Login | This event is reported when the SDK SetUser interface is called. The first time an account ID is reported, a new record is added to the account table. |
-| play_game | Game time | The SDK will start with the app entering the foreground as the timing point, and will end when the app enters the background. |
-| charge | User charge | This event is reported when the SDK charge interface is called, and it is usually recommended to use the server-side REST API for reporting. |
-
-### Derivative Events
-
-In addition to reporting preset events, TapDB also records special events, which are called derivative events. Derivative events cannot be reported directly through API, and will only be triggered by preset events.
-
-| Event Name | Event | Description |
-| --- | --- | --- |
-| dau_device | App first launch of the day | This event is triggered when the App first reports `device_login` each day and can be used to quickly query the device DAU. |
-| dvau_device | App first launch of the day (by version)| This event is triggered when the different versions of App first reports `device_login` each day and can be used to quickly query the device DAU. |
-| wau_device | App First launch of the week | App triggered when `device_login` is first reported each week, can be used to quickly query device WAU. |
-| mau_device | App First launch of the month | App triggered when `device_login` is first reported each month, can be used to quickly query device MAU.|
-| dau_user | Account first login of the day | Triggered when the account reports `user_login` for the first time every day, can be used to quickly check the account DAU. |
-| wau_user | Account first login of the week | Triggered when the account first reports `user_login` each week, can be used to quickly query the account WAU. |
-| mau_user | Account first login of the month | Triggered when the account reports `user_login` each month, can be used to quickly query the account MAU. |
-
-### Custom Events
-
-In addition to preset events and derivative events, more custom events can also be created in the event management.
-
-## Data Format
-
-TapDB's REST API supports the JSON object format after URLEncode. If you use SDK to access, the data will also be converted into this format for reporting.
-
-### Event Data
-
-Record an event and its propertys:
-
-```js
-{
- ["index" | "client_id"]: ["APPID" | "ClientID"],
- "device_id": "DeviceID",
- "user_id": "UserID",
- "type": "track",
- "name": "EventName",
- "properties": {
- "os": "Android",
- "device_id1": "000",
- "device_id2": "000",
- "device_id3": "000",
- "device_id4": "000",
- "width": 256,
- "height": 768,
- "device_model": "pixel",
- "os_version": "Android 10.0",
- "provider": "O2",
- "network": "1",
- "channel": "Google Play",
- "app_version": "1.0",
- "sdk_version": "2.8.0",
- "#custem_event_property_name": "CustomEventPropertyValue"
- }
-}
-```
-
-#### System Fields
-
-Fields at the same level as properties are system fields.
-
-| Name | Type | Description |
-| --- | --- | --- |
-| index | string | The APPID of the project, you can view the ID in TapDB backend. |
-| client_id | string | TapTap Client ID can be viewed in the TapTap Developer Center.|
-| type | string | Data type, the value of "track" when reporting events. |
-| device_id | string | The device ID at the time of the event. |
-| user_id | string | User ID at the time of the event. |
-| name | string | Event name,can be a preset event or a custom event. |
-
-Tips:
-
-- Preset events `device_login`,must also have `device_id`.
-- Preset events `user_login`, `play_game` must have `device_id` and `user_id`.
-- Preset events `charge`, must be a valid `user_id` (`user_id` was reported as `user_login`).
-
-#### Property Fields
-
-The fields inside properties are property fields, which will be the properties of the event, and you can extend them with custom properties.
-
-SDK preset event property:
-
-| Name | Type | Description |
-| --- | --- | --- |
-| os | string | Operating System(Support Android、iOS、Windows、Mac)|
-| device_id1 | string | Reserved device ID (iOS SDK uses IDFA, Android SDK uses IMEI)|
-| device_id2 | string | Reserved device ID(Android SDK uses Google ads ID)|
-| device_id3 | string | Reserved device ID(Android SDK uses Android ID)|
-| device_id4 | string | Reserved device ID(Android SDK uses OAID)|
-| width | number | Screen width |
-| height | number | Screen height |
-| device_model | string | Equipment model (equipment manufacturer + equipment model) |
-| os_version | string | OS Version |
-| network | string | Network type (WiFi is 2, unknown is 3, 2G is 4, 3G is 5, 4G is 6)|
-| channel | string | APP Packaging channels |
-| app_version | string | App version |
-| sdk_version | string | SDK version |
-
-### Property Action
-
-User property action:
-
-```js
-{
- ["index" | "client_id"]: ["APPID" | "ClientID"],
- "user_id": "UserID",
- "type": ["initialise" | "update" | "add"],
- "properties": {
- "level": 15,
- "#custom": "custom"
- }
-}
-```
-
-Device property action:
-
-```js
-{
- ["index" | "client_id"]: ["APPID" | "ClientID"],
- "device_id": "DeviceID",
- "type": ["initialise" | "update" | "add"],
- "properties": {
- "level": 15,
- "#custom": "custom"
- }
-}
-```
-
-#### System Fields
-
-At the same level as properties are the system fields.
-
-| Name | Type | Description |
-| --- | --- | --- |
-| client_id | string | TapTap Client ID can be viewed in the TapTap Developer Center. |
-| type | string | Data type, property operations support `initialise`, `update`, `add`. |
-| device_id | string | Device ID of the property operation |
-| user_id | string | User ID of the property operation |
-
-Meaning of the type field.
-
-| Name | Description |
-| --- | --- |
-| initialise | Use initialise to make only the first value valid. Only if the current value is null, the assignment will take effect, if the current value is not null, the assignment will be ignored. |
-| update | For regular device properties, you can use `update` assignment, the new property value will directly overwrite the old property value. |
-| add | For numeric properties, you can use this interface to perform accumulation operation, TapDB will accumulate the original property value and save the result value. |
-
-Tips:
-
-- Only one of `device_id` and `user_id` can be selected for property operations. Select `device_id` for the device ID attribute and `user_id` for the account ID attribute.
-
-#### Property Fields
-
-The fields inside 'properties' are property fields. These fields will be treated as fields to be manipulated and treated as' type' values.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/server-side-integration.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/server-side-integration.mdx
deleted file mode 100644
index f6617d094..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/server-side-integration.mdx
+++ /dev/null
@@ -1,230 +0,0 @@
----
-title: Server Integration Guides
-sidebar_label: Server Integration Guides
-sidebar_position: 4
----
-
-import {Conditional} from '/src/docComponents/conditional';
-
-Using the REST API, you can report data directly to TapDB without relying on the SDK.
-
-## Report Events and Properties
-
-Please refer to [Data Rules](/sdk/tapdb/sdk/data-spec#数据规则) for the format and meaning of data transfer.
-
-If the Response Code is 200, it means the data was reported successfully, please check the event writing status further in the burial site management.
-
-### Single Report
-
-
-
-`POST` `https://e.tapdb.net/v2/event`
-
-
-
-
-
-
-`POST` `https://e.tapdb.ap-sg.tapapis.com/v2/event`
-
-
-
-`Content-Type: application/json`
-
-Request content example:
-
-```json
-{
- "client_id": "test_appid",
- "device_id": "test_device_id",
- "user_id": "test_user_id",
- "type": "track",
- "name": "#EventName",
- "properties": {
- "os": "Android",
- "device_id1": "000",
- "device_id2": "000",
- "device_id3": "000",
- "device_id4": "000",
- "width": 256,
- "height": 768,
- "device_model": "pixel",
- "os_version": "Android 10.0",
- "provider": "O2",
- "network": "1",
- "channel": "Google Play",
- "app_version": "1.0",
- "sdk_version": "2.8.0",
- "#custem_event_property_name": "CustomEventPropertyValue"
- }
-}
-```
-
-### Batch Report
-
-
-
-`POST` `https://e.tapdb.net/v2/batch`
-
-
-
-
-
-`POST` `https://e.tapdb.ap-sg.tapapis.com/v2/batch`
-
-
-
-`Content-Type: application/json`
-
-Request content example:
-
-```json
-{
- "data": [
- {
- "client_id": "test_appid",
- "device_id": "test_device_id",
- "user_id": "test_user_id",
- "type": "track",
- "name": "#EventName",
- "properties": {
- "os": "Android",
- "device_id1": "000",
- "device_id2": "000",
- "device_id3": "000",
- "device_id4": "000",
- "width": 256,
- "height": 768,
- "device_model": "pixel",
- "os_version": "Android 10.0",
- "provider": "O2",
- "network": "1",
- "channel": "Google Play",
- "app_version": "1.0",
- "sdk_version": "2.8.0",
- "#custem_event_property_name": "CustomEventPropertyValue"
- }
- },
- {
- "client_id": "test_appid",
- "device_id": "test_device_id",
- "user_id": "test_user_id",
- "type": "track",
- "name": "#EventName",
- "properties": {
- "os": "Android",
- "device_id1": "000",
- "device_id2": "000",
- "device_id3": "000",
- "device_id4": "000",
- "width": 256,
- "height": 768,
- "device_model": "pixel",
- "os_version": "Android 10.0",
- "provider": "O2",
- "network": "1",
- "channel": "Google Play",
- "app_version": "1.0",
- "sdk_version": "2.8.0",
- "#custem_event_property_name": "CustomEventPropertyValue"
- }
- }
- ]
-}
-
-```
-
-### FAQ
-
-- If the subject of the current event is not a device or a user, device_id and user_id can be passed any fixed value.
-
-- To ensure that events reported on the server side can also be analyzed using the device dimension, it is recommended to call the SDK's `GetDeviceID` interface on the client side to obtain a unique ID and report it to the App's server side.
-
-## Special Type Report
-
-### Online Number
-
-Since the SDK can't push accurate online data, we provide a server-side online data push interface here. The game server can count the number of people online every 5 minutes and push it to TapDB through the interface, TapDB will summarize the data.
-
-**Note: The online count is reported in json format, which is different from other common event reporting formats, please pay attention to the difference.**
-
-
-
-`POST` `https://se.tapdb.net/tapdb/online`
-
-
-
-
-
-`POST` `https://se.tapdb.ap-sg.tapapis.com/tapdb/online`
-
-
-
-`Content-Type: application/json`
-
-Request Content:
-
-| Parameter | Type | Descriptions |
-| --- | --- | --- |
-| client_id | string | The game ClientID. |
-| onlines | array | Multiple online data (up to 100) |
-
-The structure of the onlines array is:
-
-| Parameter | Type | Descriptions |
-| --- | --- | --- |
-| server | string | TapDB accepts data only once per natural 5 minutes for the same server. |
-| online | number | Online number |
-| timestamp | number | Timestamp (in seconds) of the current statistics. TapDB will perform data alignment according to the natural 5 min. |
-
-Example:
-
-```json
-{
- "client_id":"ClientID",
- "onlines":[{
- "server":"s1",
- "online":123,
- "timestamp":1489739590
- },{
- "server":"s2",
- "online":188,
- "timestamp":1489739560
- }]
-}
-```
-
-### Recharge Record
-
-Since the SDK push may be inaccurate, it is recommended to use the server-side recharge push interface directly. Note that you need to stop the reporting of recharge information in the client SDK to prevent duplicate statistics.
-
-
-
-`POST` `https://e.tapdb.net/v2/event`
-
-
-
-
-
-`POST` `https://e.tapdb.ap-sg.tapapis.com/v2/event`
-
-
-
-`Content-Type: application/json`
-
-```json
-{
- "name": "charge", // Event name, fixed to "charge"
- "client_id": "ClientID", // Required. Note that the ClientID needs to be replaced with the game's ClientID.
- "user_id": "userId", // Required. The userId must be the same as the userId passed by the SDK's setUser interface, and the user must have been pushed through the SDK interface.
- "properties": {
- "ip": "8.8.8.8", // Optional. IP of the rechargeable user.
- "order_id": "100000", // Optional. The order ID is greater than 0 and less than or equal to 256.
- "amount": 100, // Required. Greater than 0 and less than or equal to 100000000000. recharge amount. Unit cents, i.e. whatever the currency, needs to be multiplied by 100.
- "virtual_currency_amount": 100, // Required, Number of virtual coins received, can be 0.
- "currency_type": "CNY", // Optional. Currency type. Internationally accepted three-letter expression, default CNY when empty.
- "product": "item1", // Optional. Length greater than 0 and less than or equal to 256. Product name.
- "payment": "alipay" // Optional. Length greater than 0 and less than or equal to 256. Recharge channel.
- }
-}
-```
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/user-event-model.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/user-event-model.mdx
deleted file mode 100644
index 642a94a99..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tapdb/sdk/user-event-model.mdx
+++ /dev/null
@@ -1,64 +0,0 @@
----
-title: User - Event Model
-sidebar_position: 1
----
-
-We know that when playing a game, players will have different types of behaviors and events in the game. For example:
-
-- For RPGs, users will have behavioral events such as fighting monsters, upgrading, and buying equipment within the game.
-- For PVP games, users will have behavioral events such as adding friends within the game.
-- In card games, users will have behavioral events such as purchasing cards and using cards within the game.
-
-
-Here are the users and events. We found that "Data Analysis" is analyzing **User** and **Event**.
-
-When users have various types of behavioral events, the events will have information related to Who, When, What, Where, How, etc.
-
-- **Who**:The user who triggered the Event.
-- **What**:Event.
-- **When**:Time when the Event is triggered.
-- **Where**:Location information such as IP, country, province, city, and district when the Event is triggered.
-- **How**:How the user triggered the Event, such as the type of device, OS type, OS version number, device brand, device model, device resolution, version of the game app, and other information.
-
-**Where What, When, Where, and How are all property states of the Event**.
-
-In the case of a competitive match game, "users teaming up for a match game" can be viewed as an "Event" named "match game" :
-
-- **Who**:Users who joined match game.
-- **What**: Type of match game.
-- **When**:Time of match game.
-- **Where**:IP, country, province, city, district and other location information at the time of the match game.
-- **How**:The user's phone model, system version, and phone resolution at the time of the match game.
-
-This is **Event** model and **Event** property information。**Every behavior to be analyzed in the game can be defined as Event and should be defined as Event, which is a prerequisite for data analysis.**
-
-You can configure Event information in TapDB's "Configuration" - "Event Management" page.
-
-User will also have a lot of information, such as registration time, registration address, registration channel, user level, device type, gender, age and other information, which are all property states of User. By using these properties, you can quickly filter users in behavioral analysis. or example, if we want to analyze the activity of paying users, you only need to analyze the users with the value of "accumulated payment amount" greater than 0.
-
-In the case of a competitive match game, "users teaming up for a match game" can be seen as the "who" in "event".
-
-
-- Who are the users who participate in the game?
-- How long have they been playing the game in total?
-- What is the user's score?
-- Which heroes have they played?
-- What is the user's gender?
-- What is the user's age?
-- When did the user register?
-- What is the user's registration channel?
-- What is the user's cumulative payment amount?
-
-This is the User model.
-
-**The Event model and User model are called: User-event model**.
-
-Based on the User-Event model to design buried documents and collect information, you can:
-
-- PVP games: analyze the number of participations using different characters at different levels.
-- Card games: analyze the participation of players with different VIP levels in Mid-Autumn Festival events.
-- SLG: Analyze how being robbed of resources by other players affects retention in the first 7 days of entry;
-- RPG: Focus on key churn causes by analyzing pass rates for key levels;
-- SLG: Check the resource accumulation and consumption of different levels of players for the last 7 days in order to determine how to give out gifts. It is best to achieve visual display of net inflow and outflow;
-
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-appsafety/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-appsafety/_category_.json
deleted file mode 100644
index 70785df10..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-appsafety/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "Application Security",
- "collapsed": true,
- "position": 2.1
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-appsafety/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-appsafety/features.mdx
deleted file mode 100644
index f16f9c015..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-appsafety/features.mdx
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: Introduction to Application Security Features
-sidebar_label: Function Introduction
-sidebar_position: 1
----
-
-## Introduction to Application Security
-TapTap Developer Service provides an app security module that mainly protects the security of game apps through APK reinforcement and anti-cheating detection capabilities.
-
-## Rules
-
-- Applicable games: games that have joined the Fire Plan and are running normally, or games exclusively released on the TapTap platform.
-- Entrance: Developer Centre - Select Game - Game Service - Application Security (as below)
-
-![](https://capacity-files.lcfile.com/XFKCH21Nw56UDX7B2VGXTtevPl6Eh9DY/app-safety-introduce.png)
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-appsafety/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-appsafety/guide.mdx
deleted file mode 100644
index b57466a8a..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-appsafety/guide.mdx
+++ /dev/null
@@ -1,231 +0,0 @@
----
-title: Application Security Access Guide
-sidebar_label: Guide
-sidebar_position: 2
----
-
-This article describes how to add the app security feature introduced by TapTap to your game.
-
-## Description of APK hardening function
-
-### Functional features
-
-| Function Name | Descriptions |
-| --- | --- |
-| SO Encryption Protection | Encrypt and obfuscate the code, functions and export tables of the SO files to prevent HOOK attacks and effectively improve the difficulty of unpacking. |
-| global-metadata Encrypted |For IL2CPP games, the global-metadata is hardened to raise the cracking threshold. |
-| AAB Reinforcement | Support for Android app bundle hardening solutions. |
-| Reinforcement programme self-protection | The source code of the reinforcement scheme is obfuscated, which greatly raises the threshold for reverse analysis of the reinforcement scheme by plug-in authors and further increases the difficulty of cracking the game. |
-| ROOT Environmental Testing | Reduce the potential for fraud in high-risk environments. |
-| Debugging Against | Detection of debugging tools such as IDA, FRIDA, etc., effectively raising the threshold for dynamic analysis. |
-| Simulator Recognition | Based on a sample library, it accurately identifies simulator users and provides an accurate basis for game segregation matching. |
-| Cloud Mobile Phone Recognition | Multi-dimensional determination of the cloud phone environment based on real phone data and multiple cloud phone samples from the extranet. |
-| Anti Secondary Packaging | To keep the signature consistent before and after reinforcement, you can enable signature verification to prevent secondary packaging. |
-| Malware Countermeasure | Anti-frame plug-ins, memory modifiers, multi-openers and other software. |
-
-### Operating Instructions
-
-#### Function Entrance
-
-- Games that have not joined the Fire Plan and have exclusive published on TapTap can be entered directly through the **Application Security** portal, or contact the relevant business or operation departments to apply for it if there is no such portal.
-
-- For games joined the Fire Plan, you can select the APK enhancement rights directly from the Fire Plan page and click Use Now (as shown below).
-
-![](https://capacity-files.lcfile.com/wKzxfJfVfKOxDKRlEgiQvAnQ69OdKiak/app-safety-jiagu.png)
-
-#### Rules
-
-- The number of successful reinforcement attempts for each game in a single natural month is not allowed to exceed 5 times. Developers should not abuse the service.
-- For users with multi-channel requirements, please ensure that the project is in normal operation if reinforcement is required before secondary packaging.
-- The size of the augmentation file should not exceed 2G;
-- Reinforcement does not support automatic signing and you must re-sign after reinforcement;
-- You can only reinforce a maximum of 2 files each time.
-
-## Description of the anti-cheating function
-
-The anti-cheating detection function mainly means that after the game is reinforced, the platform will monitor and capture data on illegal operations and environment when the app is running for display. By displaying cheating devices and related details, developers can gain a deep understanding of the current security situation and accurately grasp the security situation.
-
-![](https://capacity-files.lcfile.com/XJgCXueaiMHdcKDHjom7yBel6fqL8zD7/app-safety-resist.png)
-
-### Page Field Descriptions
-
-- UUID: running device tag
-- UserID: reported on the game page
-- Feature number: hexadecimal display value
-
-
-
-
-
Feature Code
-
ID
-
Detection Type
-
Detailed Information
-
-
-
1000000000000000+id
-
0
-
root class
-
root system, test signature pattern
-
-
-
1
-
root
-
root、magisk、TESTSIGNING
-
-
-
2000000000000000+id
-
0
-
Debugging Classes
-
Debugger
-
-
-
1
-
android debug
-
ida、frida、java Information
-
-
-
3
-
android ADB
-
adb enabled
-
-
-
3000000000000000+id
-
0
-
hook class
-
hook
-
-
-
1
-
android hooked, iOS inline hook
-
api flag
-
-
-
2
-
android hook frame, iOS fh hook
-
hook frame
-
-
-
3
-
android java hooked
-
java hook flag
-
-
-
5
-
android hook framework (multi-opener))
-
-
-
-
4000000000000000+id
-
0
-
Memory Classes
-
Runtime memory modified externally
-
-
-
1
-
Determining when a memory device is open
-
-
-
-
5000000000000000+id
-
0
-
Resource Modification Class
-
Resource, Signature Modified
-
-
-
1
-
android signature, iOS exception signature team
-
apk or so signature information
-
-
-
6000000000000000+id
-
0
-
Emulator Classes
-
Running with an emulator
-
-
-
any
-
Emulator
-
Type
-
-
-
-
7000000000000000+id
-
0
-
Cloud Phone Class
-
Running with a Cloud Phone
-
-
-
any
-
Cloud Phone
-
Type
-
-
-
8000000000000000+id
-
0
-
Generic Plug-ins, Peripherals Classes
-
-
-
-
any
-
Generic Plug-ins, Peripherals type value
-
Peripheral
-
-
-
-
B000000000000000+id
-
0
-
Sandbox Classes
-
-
-
-
any
-
Sandbox Detection Type
-
Type
-
-
-
-
-
-
-- Detection type
-
-| index | type | description |
-| --- | --- | --- |
-| 1 | Root Class | android and ios: running environment is root environment, windows: test signature environment |
-| 2 | Debugging Class | Running processes are debugged |
-| 3 | Hook Class | hooked |
-| 4 | Memory Modifier Class | Running content has been modified |
-| 5 | Resource Modification Class | Resource signatures have been modified |
-| 6 | Simulator Class | Simulator running |
-| 7 | Cloud Provider Class | Cloud real machine operation |
-| 8 | General Plug-ins | Known types of cheating software detection |
-| 9 | Special Plug-ins | Peripheral Input Capture Calculation |
-| 10 | Injection Class | Cheat frames or other injections |
-| 11 | Sandbox Class | Sandbox environment operation |
-
-- Detection results
-
-1. Implement processing strategies according to detection types.
-
-2、Detect to stop or do not perform additional operations.
-
-- Detailed information
-
-1、Detection type details
-
-
-### Function usage rules
-
-#### Diagram operation
-
-- Provide filter function based on report ID, user ID, detection type, detection result and time.
-- Provide report download function to support downloading the detected data to the local area.
-
-![](https://capacity-files.lcfile.com/skGGCntpLlMUqjeQOMESC2mHQs7dOG9q/app-safety-diagrams.png)
-
-### Anti-cheat strategy configuration
-
-- Anti-cheating strategy configuration is provided, and developers can configure their own anti-cheating strategies.
-- Configuration is enabled by default and supports configuration. Developers can disable it if they wish. After it is disabled, the corresponding configured strategy will not take effect.
-
-![](https://capacity-files.lcfile.com/ItAilmNzJsHD2usODnWMLHIqahLDt9Ow/app-safety-cheat.png)
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-connect/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-connect/_category_.json
deleted file mode 100644
index 0fac2fad0..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-connect/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "TapTap Connect (Floating Window) ",
- "collapsed": true,
- "position": 2.5
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-connect/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-connect/features.mdx
deleted file mode 100644
index 17dab3b92..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-connect/features.mdx
+++ /dev/null
@@ -1,51 +0,0 @@
----
-title: TapTap Connect Features
-sidebar_label: Features
-sidebar_position: 1
----
-
-## Products
-TapTap Connect (hereafter referred to as the Floating Window) is a tool that connects TapTap, games and players. Once a developer has enabled this feature, players who have successfully logged in their TapTap accounts can browse game updates, rate games and share game download links in the Floating Window to help improve retention and bring in new users.
-
-
-![](https://capacity-files.lcfile.com/NiPOqorbHRaoXCJvgoAmJzXXvKwJnMCU/461703147053_.pic_hd.jpg)
-
-
-## Player Interaction Flow
-
-- After logging into the game with TapTap, a Floating Window portal will appear, which is semi-hidden by default and supports drag and drop as well as hovering.
-- Clicking on the portal will open the Floating Window panel, which displays the player's TapTap account information (avatar, nickname and ID), and also allows the player to open embedded updates, share games, and jump to the TapTap game page to rate the game from within the Floating Window. (The rating feature is only supported by the Android system.)
-- Players who click on the blank space outside the Floating Window panel will close the Floating Window and the portal will automatically return to a semi-hidden state after 3 seconds.
-
-## Functional Advantages
-
-TapTap community content, review sharing and other capabilities are integrated so that players can perform related operations without leaving the game.
-
-- Firstly, it can help the game increase retention and bring in new users;
-
-- Secondly, it reduces the amount of access development for developers. After accessing the Floating Window, there is no need to separately access embedded dynamics, open the comment area, share and other features.
-
-## Instructions for use
-
-### Pre-Access Operation
-- The game needs to access the TapTap Login feature and enable the embedded moments service.
-
-- Enable the Floating Window feature in the [Game Services - TapTap Login - Floating Window ] module in the Developer Centre (the Floating Window feature is disabled by default and must be enabled by operation before it is displayed to players).
-
-## Theme Style Configuration Instructions
-
-### 1、Supports customisation of the Floating Window portal
-
-- Using the icons directly provided by the platform by default.
-
-- TapTap avatar can be used directly as the entrance icon.
-
-- Developers should follow the design specification and design their own entrance icon to match the game.
-
-### Supports customizing the background of the user information area of the Floating Window panel
-
-- By default, the backgrounds provided by the platform are used directly
-
-- Developers follow the design specifications and design their own backgrounds to match the game.
-
-![](https://capacity-files.lcfile.com/FbnCykRXSvU2UW9roMmF1Npta6pguw1I/451703147034_.pic_hd.jpg)
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-connect/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-connect/guide.mdx
deleted file mode 100644
index f74952b84..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-connect/guide.mdx
+++ /dev/null
@@ -1,209 +0,0 @@
----
-title: Floating Window Developer Guide
-sidebar_label: Developer Guide
-sidebar_position: 2
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-import Languages from "../_partials/languages.mdx";
-import UnitySDKInstallation from "../_partials/unity-sdk-installation.mdx";
-
-This article describes how to add [TapTap Floating Window](/sdk/taptap-connect/features/) to your game. Using the Floating Window feature relies on TapTap Login and TapTap Moment.
-
-
-## Environmental requirements
-
-
-
-<>
-
-- TapSDK **3.21.0** and above
-- Unity 2019.4 or higher
-- iOS 11 or later, Xcode version [14.1 or later](https://developer.apple.com/news/?id=jd9wcyov)
-- Android 5.0 (API level 21) or later
-
->
-
-<>
-
-- TapSDK **3.21.0** and above
-- Android 5.0 (API level 21) or later
-
->
-
-<>
-
-- TapSDK **3.21.0** and above
-- iOS 11 or later, Xcode version [14.1 or later](https://developer.apple.com/news/?id=jd9wcyov)
-
->
-
-<>
-
-- TapSDK **3.22.0** and above
-- Install UE 4.26 and above
-- iOS 12 or higher
-- Android 5.0 (API level 21) or higher
-- macOS 10.14.0 or later
-- Windows 7 or higher
-
-**Supported platforms**: Android / iOS / Windows / macOS
-
->
-
-
-
-## Pre-integration preparation
-
-1. Refer to [Preparation](/sdk/start/get-ready/) to create the application and turn on the Floating Window settings accordingly;
-
-## SDK Acquisition
-Please refer to [TapTap Login](/sdk/taptap-login/guide/tap-login/#getting-the-sdk) and [embedded-moments](/sdk/embedded-moments/guide/#installing-sdk) to complete the SDK obtaining, and then you can get the TapSDK through [download](/tap-download) to add the `TapConnect` module on top of it:
-
-
-
-
-
-
-
- {`repositories{
- flatDir {
- dirs 'libs'
- }
-}
-dependencies {
- ...
- implementation (name:'TapConnect_${sdkVersions.taptap.android}', ext:'aar') // TapTap Floating Window
- implementation (name:'TapMoment_${sdkVersions.taptap.android}', ext:'aar') // TapTap Floating windows rely on inline dynamics - Mandatory
- implementation (name:'TapLogin_${sdkVersions.taptap.android}', ext:'aar') // TapTap Floating Window Dependent Login Module - Required
- implementation (name:'TapCommon_${sdkVersions.taptap.android}', ext:'aar') // Required: TapSDK Base Library
- implementation (name:'TapBootstrap_${sdkVersions.taptap.android}', ext:'aar') // Optional: TapSDK Base Library
-}`}
-
-
-
- {`// TapTap Hover
-TapConnectResource.bundle
-TapConnectSDK.framework
-// Floating Window relies on inline dynamics and login module - Mandatory
-TapMomentResource.bundle
-TapMomentSDK.framework
-TapLoginSDK.framework
-TapLoginResource.bundle
-// Basic Module - Mandatory
-TapCommonResource.bundle
-TapCommonSDK.framework
-// Basic Module - Optional
-TapBootstrapSDK.framework
-`}
-
-
-```cs
-PublicDependencyModuleNames.AddRange(new string[] {
- //...
- "TapConnect" //Mandatory
- "TapLogin" //Mandatory-Floating Window relies on the login module
- "TapMoment" //Mandatory-Floating Window relies on inline dynamics
- "TapCommon" //Mandatory-Basic Module
- "TapBootstrap"//Optional-Basic Module
-});
-```
-
-
-
-## Initialisation
-
-:::info
-Choose either of the following two initialisation methods.
-:::
-
-### TapSDK Initialisation
-
-If you have already done the initialisation of [built-in account system Tap Login](/sdk/taptap-login/guide/tap-login/#initialization), you only need to introduce the Floating Window module here, no other additional processing is needed.
-
-
-### Separate initialisation of the Floating Window
-
-If the game does not initialise the TapSDK via the `TapBootstrap` method provided above, the Floating Window can be initialised separately, as the Floating Window relies on pure Tap authentication and inline dynamics.
-
-:::tip
-
-Initialising the Floating Window alone requires that the `only TapTap authentication` and `inline dynamic` initialisation actions are prioritised. For TapTap authentication-only initialisation [reference](/sdk/taptap-login/guide/tap-login/#initialization), and for embedded dynamic initialisation [reference](/sdk/embedded-moments/guide/#setting-up-callbacks).
-:::
-Below is the sample code to initialise the Floating Window separately:
-
-
-
-```cs
-using TapTap.Connect; // namespace
-
-TapConnect.Init("your_client_id", "your_client_token", (bool)isCN);
-```
-
-```java
-//activity is the current Activity instance
-TapConnect.init(activity, "clientId", "clientToken", isCN);
-```
-
-```objc
-[TapConnect initWithClientId:@"clientId" clientToken:@"clientToken" isCN:YES];
-```
-
-```cpp
-FTapConnect::Init("clientId", "clientToken", true);
-```
-
-
-
-### Parameter description
-
-* The `client_id` and `client_token` information can be viewed at **Developer Centre > Your Games > Game Services > Application Configuration**, and isCN indicates whether the application is from Mainland China.
-
-## Setting the Floating Window portal to show hidden
-
-Sometimes developers want to directly control the display and hiding of the Floating Window entry in some scenarios, e.g. only display the Floating Window entry in some scenarios, then they can call the following interface:
-
-
-
-```cs
-TapConnect.SetEntryVisible(bool visible)
-```
-
-```java
-TapConnect.setEntryVisible(boolean visible);
-```
-
-```objc
-[TapConnect setEntryVisible:YES];
-```
-
-```cpp
-FTapConnect::SetEntryVisible(true);
-```
-
-
-
-## Internalisation
-
-The Floating Window supports setting the language:
-
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/_category_.json
deleted file mode 100644
index ee0a19161..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "TapTap 登录",
- "collapsed": true,
- "position": 2
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/best-practice.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/best-practice.mdx
deleted file mode 100644
index 13b291ded..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/best-practice.mdx
+++ /dev/null
@@ -1,56 +0,0 @@
----
-title: Best TapTap Login Practices
-sidebar_label: Best Practices
-sidebar_position: 3
----
-
-import useBaseUrl from '@docusaurus/useBaseUrl';
-
-## Login Process
-
-Conversion rate increases when players have a streamlined login process. Use the simplest method that retains all necessary steps, so players can quickly enter the game. As shown below:
-
-
-
-### Login Interface
-
-Provide players with a TapTap Login button designed according to [Login Design Guide](/design/). Refer to [Feature Summary](/sdk/taptap-login/features/) for information on displaying single/multiple login methods.
-
-### Using Players' Public Info
-
-When players create characters in-game, the system can utilize authorized public info upon logging into the game, such as the player's TapTap avatar, nickname, etc., to help players complete the registration process automatically.
-
-If you are using the TDS Authentication system, refer to [Setting Other Users' Properties](/sdk/authentication/guide/#Setting Other Users' Properties) section to set user info.
-
-If you are using the basic TapTap Login, refer to the following flow chart:
-
-
-
-*Reference the TapTap Login Guide.
-
-### Provide a Switch Account Function
-
-We recommend providing players with a Switch Account function in-game.
-
-- When switching accounts, the player will be logged out. This guarantees that other in-game services (i.e. Moments) stay consistent with the logged-in account.
-- When players have logged out, automatically display the login screen with the option for players to log in with another account.
-
-### Provide a Bind Account Function
-
-We recommend providing players with a Bind Account function. This gives players additional login methods.
-
-If you are using TDS Authentication to create the account system, refer to [Binding Third-Party Accounts](/sdk/authentication/guide/#binding-third-party-accounts) section.
-
-If you have your own account system and only use the basic TapTap Login, the game must have a Bind Account function so players can bind their game account with their unique ID returned after logging into TapTap.
-
-## Checklist
-
-Prior to providing players with a login function, developers must test the login process to see if it can be completed as intended. Check the following:
-
-- Whether the game meets [SDK Environment Requirements](/sdk/start/quickstart/#environment-requirements).
-- Whether the developer understands the two TapTap Login methods in TapSDK. See [Integrating TapTap Login](/sdk/taptap-login/guide/start/).
-- Whether the TapTap developer added the corresponding Android or iOS platform configurations to the backend. See [Configure Signature Certification](/sdk/start/quickstart/#configure-signature-certificate) and [Format Requirements](/sdk/taptap-login/features/#format-requirements).
-- Whether users without the TapTap app can log in using WebView. Then check if the developer is able to obtain players' authorized basic info.
-- Whether users with the most recent TapTap app can open the game and log in. Then check if the developer is able to obtain players' authorized basic info.
-- Whether Silent Login is available after logging out once login authorization is complete. See [Silent Login](/sdk/taptap-login/features/#performing-silent-login).
-- Whether the login process will restart upon leaving and re-entering the game without completing login authorization.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/faq.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/faq.mdx
deleted file mode 100644
index d457b2c46..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/faq.mdx
+++ /dev/null
@@ -1,59 +0,0 @@
----
-title: Frequently Asked Questions
-sidebar_label: Frequently Asked Questions
-sidebar_position: 4
----
-
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-import { Conditional } from "/src/docComponents/conditional";
-
-### TapTap login reports `The requested scope (compliance) does not match any of the allowed scopes` exception
-Check if the Real-Name Authentication & Anti-Addiction SDK package has been imported but not initialized. If you don't need the Real-Name Authentication service, don't import the Real-Name Authentication package into your project.
-
-### TapTap For iOS login reports `accessToken: sdk_not_matched` exception
-Check that the app has the bundle ID configured in the TapTap developer backend.
-
-### TapTap login throws `signature not match` exception
-This exception is thrown if you have a TapTap login but have not configured a signature or have configured it incorrectly in the Developer Centre backend. Some developers may not be able to debug the exception and can verify it this way: If the TapTap client is uninstalled and the WebView authorisation pops up when testing the login function, but the test device has the TapTap client installed, it will not pull up the client authorisation, this is basically due to a signature configuration issue, please refer to [documentation](/sdk/start/quickstart/#configure-signature-certificate) to complete the configuration.
-
-### TapTap login prompt `Not in beta or not in beta, can't login to game` exception prompt.
-**TapTap Developer Center > Store > Releases > Internal Testing** Check to see if Internal Testing is enabled, and if so, make sure your current TapTap ID account has not been added as a test user.
-
-### TapTap login reports `state not equal` exception
-Check if the system time of the current device is already synchronised with the network and if the TapTap client version of the current device is too low.
-
-### TapTap Login reported a `java.lang.NoSuchFieldException: CACHE_ELSE_NETWORK' exception
-The TapSDK is already obfuscated because the Android project has enabled obfuscation, so you need to skip obfuscating the TapSDK. Please see [Android Code Obfuscation](/sdk/start/quickstart/#android-code-obfuscation) for details. Please disable resource obfuscation if it is enabled in your project, `shrinkResources false`.
-
-### TapTap login reported `{"code":36869,"error_description": "Unauthorised."}` Exception
-Check that the Client ID, Client Token and ServerURL parameters of the TapSDK initialisation are set correctly. Also **use the HTTPS protocol** for the ServerURL and refer to the documentation on **[domain](/sdk/start/get-ready/#domain)**.
-
-### TapTap Login reports `chain validation failed` exception
-The following steps can be used to troubleshoot this issue:
-1. Check that the device is connected to the proxy and that the certificate is installed correctly.
-2. Check that the telephone system time has not been changed.
-
-### TapTap Login reports `application id is empty` exception
-1. Check that the initialisation operations of the TapSDK are being performed in the Android UI thread, i.e. the main thread;
-2. Ensure that TapSDK initialisation is complete, to avoid calling the TapTap login function immediately after TapSDK initialisation, it is recommended that TapSDK initialisation is done before.
-
-### Simple TapTap user authentication for Unity integration reports the following exception
-
-```
-Assembly 'Assets/TapTap/Common/Plugins/TapTap.Common.dll' will not be loaded due to errors:
-Unable to resolve reference 'LC.Newtonsoft.Json'. Is the assembly missing or incompatible with the current platform?
-```
-The reason for this error is that the `com.leancloud.storage` module has not been added to the project's `Packages/manifest.json` file when using TapSDK Unity v3.7.1 and above, see [documentation](/sdk/taptap-login/guide/tap-login/#getting-the-sdk) to add it.
-
-### Login prompt: this application is not allowed for this domain
-
-1. Check that the TapTap login service is enabled in the developer backend application configuration;
-2. Check that the ClientId, ClientToken, ServerUrl (must start with `https://`) in the project initialisation code are consistent with the developer backend.
-
-
-![](https://dc-file.leanticket.cn/VIFonJ9YOJ4SAXb3WdVhMYWlF84xGKkN/CF18E02C-C819-4940-9C9B-60050062EDD0.png)
-
-### Can a developer get a player's mobile phone number after they have logged in using TapTap?
-
-No. Mobile phone numbers are private player information and developers are not currently allowed to obtain mobile phone numbers from logged-in players.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/features.mdx
deleted file mode 100644
index 915a8faf0..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/features.mdx
+++ /dev/null
@@ -1,120 +0,0 @@
----
-title: TapTap Login Features
-sidebar_label: Features
-sidebar_position: 1
----
-
-import useBaseUrl from "@docusaurus/useBaseUrl";
-import { Conditional } from "/src/docComponents/conditional";
-
-You must have a TapTap user account to access TapTap Developer Services (TDS). Otherwise, your application may encounter errors when calling the TDS API. This document describes how to implement TapTap Login into your application.
-
-## Service Info
-
-TapTap Account Services is an authorized login system based on the standard OAuth 2.0 protocol. It provides developers with a simple, safe and fast account login authorization function that eliminates the need for users to enter their account password. Users will be able to instantly log into your application with a click of a button by authorizing their TapTap account.
-
-After obtaining user authorization, developers can use interface call to obtain TapTap users' relevant public information, including user nickname, avatar and other information, which can be used to improve the user experience within the application.
-
-## Preliminary Work
-
-Confirm that the startup operation has been completed in **TapTap Developer Center > Game Services > Configuration**. See [Before You Start](/sdk/start/get-ready/) in the Getting Started Guide.
-
-### Configure Signature Certificate
-
-In order to improve security, TapTap Login services must verify your game. You need to submit the Android package name, iOS bundle name, and Android signature for the game.
-
-:::tip
-
-1. For the Android package name, please use the naming method according to Android specifications. See document: [Android Developer - Set Application ID](https://developer.android.com/studio/build/application-id)
-
-2. The Android signature is the MD5 string (32 bits) in the Keystore file. Please remove special symbols when filling it in.
-
-3. iOS Bundle ID should be named according to Apple specifications. See document: [Property List Key - CFBundle Identifier](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleidentifier)
-
-:::
-
-## Interactive Login Implementation
-
-If there is no user login status, developers must provide users with an interactive login interface for users to click. The TapTap review team will review your login interface when the app is put on the TapTap Store. See [Login Button Design Specifications](/design/) for creation.
-
-### Single Login Method
-
-If the application's only login method is through TapTap Login, we recommend creating an interactive login button on the start menu of the game. The shape and size of the button should not mislead or hinder the normal login procedure.
-
-Login button design can include game elements consistent with [Login Button Design Specifications](/design/). In addition, TDS also has TapTap Login button designs for various application scenarios available to quickly implement login. Click [TapTap Login Button Design Icon](/tap-download) to download resources.
-
-
-
-
-
-
-
-
-
-
-
-
-
-### Multiple Login Method
-
-If the game has additional login methods, developers should provide users with a reasonably arranged login interface that clearly distinguishes the various login methods. This should allow users to quickly find their preferred login method.
-
-
-
-## Silent Login Implementation
-
-Silent Login can help users expedite the login process. This feature is typically used in scenarios where users still have login statuses upon restarting the game.
-
-When a user starts the game, you can check whether the user has logged in on the current device and whether the login information is still valid.
-
-- For using the TDS Authentication method, refer to [Check Login Status](/sdk/taptap-login/guide/start/#check-login-status)
-- For Basic TapTap Login, refer to [Check Login Status and User Information](/sdk/taptap-login/guide/tap-login/#check-login-status-and-user-info).
-
-This will allow you to help users complete the login process without displaying a login button or interface.
-
-## Login Authorization
-
-TapTap Account Services for mobile applications need to be used in conjunction with the TapTap Mobile Client. TapSDK will automatically use the appropriate login process according to the TapTap Client and user's device.
-
-
- [Click here](https://www.taptap.cn/mobile)
-
-
- [Click here](https://www.taptap.io/mobile)
- to download the TapTap Mobile Client.
-
-### Calling Authorized Login for the TapTap Client
-
-When the user clicks the TapTap login button and the TapSDK detects the TapTap Client installed on the user's device, it will automatically call the TapTap client, identify the login information, and authorize the login.
-
-
-
-
-
-
-
-
-
-
-
-
-
-### Open Authorized WebView Login
-
-
-
-If the user clicks the TapTap login button and TapSDK does not detect the TapTap Client on the user's device, it will open WebView for the login process.
-
-
-
-
-
-
-
-
-
-If the user clicks the TapTap login button and TapSDK does not detect the TapTap Client on the user's device, it will be prompted to download the TapTap client.
-
-
-
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/guide/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/guide/_category_.json
deleted file mode 100644
index 3446da847..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/guide/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "开发指南",
- "collapsed": true,
- "position": 2
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/guide/start.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/guide/start.mdx
deleted file mode 100644
index d072cccab..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/guide/start.mdx
+++ /dev/null
@@ -1,272 +0,0 @@
----
-title: Integrate TapTap Login
-sidebar_label: Integrate Functions
-sidebar_position: 0
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import { Conditional } from "/src/docComponents/conditional";
-import Profiles from "../../_partials/tap-login-profile.mdx";
-
-Integrate TapTap Login Methods:
-
-1. Integrate TapTap login via [TDS Authentication System](/sdk/authentication/features).
-2. [Basic TapTap User Verification](/sdk/taptap-login/guide/tap-login/).
-
-We recommend the first method for the following scenarios:
-
-- Integrating the account system provided by TapSDK.
-- Allowing players to bind additional third-party accounts to their account (Ex: QQ, WeChat, Apple).
-- Integrating basic TapSDK system functions in TDS Authentication such as Friends or Achievements.
-
-Otherwise, if you already have an account system and do not plan on using TapSDK functions such as Friends or Achievements, you can integrate TapTap User Login via the second method.
-
-We will introduce the first method and then the [second method](/sdk/taptap-login/guide/tap-login/).
-
-Both methods require visiting **Developer Center > Game Services > Integrate Functions** to activate TapTap Login.
-
-
-
-
-## Check Login Status
-
-The SDK will save the user's login data in the local cache. When your game launches, you can retrieve the login data using the following code. This will allow the player to enter the game without having to log in.
-Cache will not automatically be cleared.
-
-If the player logs out of the game or clears the game's cache, then the login info will also be deleted.
-
-
-
-```cs
-var currentUser = await TDSUser.GetCurrent();
-if (null == currentUser)
-{
- Debug.Log("Not logged in");
- // Start logging in
-}
-else
-{
- Debug.Log("Logged in");
- // Enter the game
-}
-```
-
-```java
-if (null == TDSUser.currentUser()) {
- // Not logged in
-} else {
- // Logged in; enter the game
-}
-```
-
-```objectivec
-TDSUser *currentUser = [TDSUser currentUser]
-if (currentUser == nil) {
- // Not logged in
-} else {
- // Logged in; enter the game
-}
-```
-
-
-
-## Quick Log-in With TapTap
-
-Regarding TapTap User Login, TapSDK provides special support to developers with the fastest and most convenient integration.
-
-You can call `TDSUser.loginWithTapTap` to log in with a press of a button. For example:
-
-
-
-```cs
-try
-{
- // On iOS and Android, the TapTap app will be called or WebView will be used to log in.
- // On Windows and macOS, the SDK will display a QR code (default) and a jump link (to be configured).
- var tdsUser = await TDSUser.LoginWithTapTap();
- Debug.Log($"login sucess:{tdsUser}");
- // Get properties of TDSUser
- var objectId = tdsUser.ObjectId; // Unique ID
- var nickname = tdsUser["nickname"]; // Nickname
- var avatar = tdsUser["avatar"]; // Avatar
-}
-catch (Exception e)
-{
- if (e is TapException tapError) // using TapTap.Common
- {
- Debug.Log($"encounter exception:{tapError.code} message:{tapError.message}");
- if (tapError.code == TapErrorCode.ERROR_CODE_BIND_CANCEL) // Cancel Login
- {
- Debug.Log("Login cancelled");
- }
- }
-}
-```
-
-```java
-TDSUser.loginWithTapTap(MainActivity.this, new Callback() {
- @Override
- public void onSuccess(TDSUser resultUser) {
- Toast.makeText(MainActivity.this, "succeed to login with Taptap.", Toast.LENGTH_SHORT).show();
- // Developers can call the resultUser method to obtain additional properties.
- String userId = resultUser.getObjectId(); // Unique User ID
- String avatar = (String) resultUser.get("avatar"); // Avatar
- String nickName = (String) resultUser.get("nickname"); // Nickname
- }
-
- @Override
- public void onFail(TapError error) {
- Toast.makeText(MainActivity.this, error.getMessage(), Toast.LENGTH_SHORT).show();
- }
-}, "public_profile");
-```
-
-```objectivec
-[TDSUser loginByTapTapWithPermissions:@[@"public_profile"] callback:^(TDSUser * _Nullable user, NSError * _Nullable error) {
- if (user) {
- // Developers can call the user method to obtain additional properties.
- NSString *userId = user.objectId;
- NSString *username = user[@"nickname"];
- NSString *avatar = user[@"avatar"];
- } else {
- NSLog(@"%@", error);
- }
-}];
-```
-
-
-
-After calling the interface above, the TapTap client or a WebView will be opened up to start the log-in process. Once the user finishes authorizing, your app can use the result of the OAuth to finish logging in to the TDS account system.
-
-`TDSUser` is the current user's account system. Upon logging in, developers can:
-
-- Visit `objectId` to obtain the user's system ID (the unique identifier), which can be used to bind or match the player on the game server with TDS Authentication.
-- Visit `nickname` properties to obtain the user's TapTap account name.
-- Visit `avatar` properties to obtain the TapTap account's avatar.
-
-You can [view and manage user accounts](/sdk/authentication/features/#admin-console) by going to the Developer Center.
-
-### Obtain User Details
-
-Once TapTap user login is complete, developers can use the following methods to obtain information on TapTap authorization results:
-
-
-
-```cs
-var profile = await TapLogin.FetchProfile();
-Debug.Log($"profile: {profile.ToJson()}");
-```
-
-```java
-Profile profile = TapLoginHelper.getCurrentProfile();
-```
-
-```objectivec
-[TapLoginHelper currentProfile]
-```
-
-
-
-
-
-
-## User Logout
-
-Players can easily log out by calling `logOut`.
-
-
-
-```cs
-await TDSUser.Logout();
-```
-
-```java
-TDSUser.logOut();
-```
-
-```objectivec
-[TDSUser logOut];
-```
-
-
-
-## PC Login Configurations
-
-:::tip
-
-Unity SDK 3.5.2 onwards on Windows and macOS supports using a QR code or jump link for players to access the TapTap login page.
-
-SDK **supports QR scanning to log in by default**.
-
-Jump links must be configured. See below for examples:
-
-:::
-
-
-
-![PC Login](https://capacity-files.lcfile.com/BGyFDAQUNUrw9EuMcx8SyP7pek4BKz5u/taptap-login-pc.png)
-
-
-
-
-
-![PC Login](https://capacity-files.lcfile.com/GI0d6OOdTq4Xaphumdiayqi16JBgbmMg/taptap-login-pc.png)
-
-
-
-### Windows
-
-Using a jump link on Windows requires filling out the following configurations in the Registry:
-
-```
-Windows Registry Editor Version 5.00
-
-[HKEY_CLASSES_ROOT\open-taptap-{client_id}]
-@="{Game Name}"
-"URL Protocol"="{Program.exe installation path}}"
-
-[HKEY_CLASSES_ROOT\open-taptap-{client_id}]
-@="{Game Name}"
-
-[HKEY_CLASSES_ROOT\open-taptap-{client_id}]
-
-[HKEY_CLASSES_ROOT\open-taptap-{client_id}\Shell\Open]
-
-[HKEY_CLASSES_ROOT\open-taptap-{client_id}\Shell\Open\Command]
-@="\"{Program.exe installation path}\" \"%1\""
-```
-
-### macOS
-
-On macOS, SDK will automatically configure `CFBundleURLTypes`.
-
-Use the following steps to configure without errors:
-
-- On Unity, open `BuildSetting` and select `PC, Mac & Linux Standalone` Platform. In `Target Platform`, select `macOS`.
-- Check `Create XCode Project`, select `XCode` to compile.
-- Open output `XCode Project`, select `Target`, click `Info`, open `URL Types`, inspect whether you have input the `URL Scheme`: `TapWeb : open-taptap-{clientId}`. If not, then enter:
-
-```xml
-CFBundleURLTypes
-
-
- CFBundleURLName
- TapWeb
- CFBundleURLSchemes
-
- open-taptap-{client_id}
-
-
-
-```
-
-## Additional Functions
-
-View the [TDS Authentication Guide](/sdk/authentication/guide/) for info on the other functions of TDS Authorization.
-
-
-## Video Tutorials
-
-You can refer to the video tutorial:[How to Integrate TapTap Login Functionality in Games](https://www.bilibili.com/video/BV1YX4y1b7TB/) to learn how to access login functionality in Untiy projects.
-
-For more video tutorials, see [Developer Academy](https://developer.taptap.cn/tds-tutorials/list). As the SDK features are constantly being improved, there may be inconsistencies between the video tutorials and the new SDK features, so the current documentation should prevail.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/guide/tap-login.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/guide/tap-login.mdx
deleted file mode 100644
index cad38c3a2..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/guide/tap-login.mdx
+++ /dev/null
@@ -1,371 +0,0 @@
----
-title: Basic TapTap User Verification
-sidebar_label: Basic Verification
-sidebar_position: 1
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import { Conditional } from "/src/docComponents/conditional";
-import Profiles from "../../_partials/tap-login-profile.mdx";
-
-If you only wish to use TapTap as a login method and do not wish to use any other TDS cloud services, reference this document. Note: If you only select TapTap Login, choosing other cloud services at a later time may result in an upgrade fee.
-
-
-
-Developers using TapSDK v1.x can reference this guide to upgrade TapSDK.
-
-
-
-## Getting the SDK
-
-Integrating the basic TapTap Login requires the `TapLogin` and `TapCommon` modules. Please first [download](/tap-download) the TapSDK and add the corresponding dependencies.
-
-## Initialization
-
-
-
-:::note
-When opening the app's configuration settings, select the game's [**Applicable Region**](/sdk/start/get-ready/#applicable-region).
-During initialization, you must indicate whether the app will be used in Mainland China or other countries and regions.
-:::
-
-
-
-
-<>
-
-
-
-```cs
-// Used in Mainland China
-TapLogin.Init(string clientID);
-
-// Used in other countries and regions
-TapLogin.Init(string clientID, bool isCn, bool roundCorner);
-```
-
-
-
-
-
-```cs
-TapLogin.Init(string clientID, bool isCn, bool roundCorner);
-```
-
-
-
-**Parameter Summary**
-
-| Parameter | Description |
-| ----------- | --------------------------------------------------------------------------------------------------------------------------------- |
-| clientID | Determines an app's corresponding Client ID in the TapTap Developer Center |
-| isCn | Depends on the [applicable region](/sdk/start/get-ready/#applicable-region). `true` for mainland China, `false` for International |
-| roundCorner | Determines if the web page border is rounded when logging in. Rounded=true, straight=false |
-
->
-<>
-
-
-
-```java
-// Used in Mainland China
-TapLoginHelper.init(Context context, String clientID);
-
-// Used in other countries and regions
-LoginSdkConfig config = new LoginSdkConfig();
-config.regionType = RegionType.IO;
-TapLoginHelper.init(Context context, String clientID, config);
-```
-
-
-
-
-
-```java
-LoginSdkConfig config = new LoginSdkConfig();
-config.regionType = RegionType.IO;
-TapLoginHelper.init(Context context, String clientID, config);
-```
-
-
-
-**Parameter Summary**
-
-| Parameter | Description |
-| --------- | -------------------------------------------------------------------------- |
-| context | Context generally refers to the current application |
-| clientID | Determines an app's corresponding Client ID in the TapTap Developer Center |
-
->
-<>
-
-
-
-```objectivec
-// Used in Mainland China
-[TapLoginHelper initWithClientID:clientID];
-
-// Used in other countries and regions
-TTSDKConfig *config = [[TTSDKConfig alloc] init];
-config.regionType = RegionTypeIO;
-config.roundCorner = YES;
-[TapLoginHelper initWithClientID:clientID config:config];
-```
-
-
-
-
-
-```objectivec
-TTSDKConfig *config = [[TTSDKConfig alloc] init];
-config.regionType = RegionTypeIO;
-config.roundCorner = YES;
-[TapLoginHelper initWithClientID:clientID config:config];
-```
-
-
-
-**Parameter Summary**
-
-| Parameter | Description |
-| ----------- | ------------------------------------------------------------------------------------------------------- |
-| clientID | Determines an app's corresponding Client ID in the TapTap Developer Center |
-| regionType | Refers to the conditional region. Mainland China=`RegionTypeCN`,other countries/regions=`RegionTypeIO` |
-| roundCorner | Determines if the corners are rounded |
-
-**Transferring Configurations to TapTap Applications**
-
-If the user doesn't have the TapTap application, WebView will open by default.
-
-Open info.plist to add the following configurations, then switch the clientID to the clientID obtained from your console.
-
-```xml
-CFBundleURLTypes
-
-
- CFBundleTypeRole
- Editor
- CFBundleURLName
- taptap
- CFBundleURLSchemes
-
- tt[clientID]
-
-
-
-
-LSApplicationQueriesSchemes
-
- tapiosdk
- tapsdk
- taptap
-
-```
-
-If the project has SceneDelegate.m, delete it and add the following code to the AppDelegate.m file.
-
-```objectivec
-#import
-- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
- return [TDSHandleUrl handleOpenURL:url];;
-}
-
-- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
- return [TDSHandleUrl handleOpenURL:url];;
-}
-```
-
-Add UIWindow to AppDelegate.h, then delete the Application Scene Manifest in the info.plist.
-
-```objectivec
-@property (strong, nonatomic) UIWindow *window;
-```
-
->
-
-
-
-## TapTap Login and Retrieving Login Results
-
-:::tip
-After the user opens the game, the game can check the [login status of the user](#check-login-status-and-user-info). If there is already a user logged in, the game can let the user proceed to the game without having to manually log in again.
-:::
-
-
-
-```cs
-try
-{
- // On iOS and Android, the SDK will call the TapTap app or WebView to initiate login.
- // On Windows and macOS, the SDK will display a QR code (default) and a jump link (to be configured).
- var accessToken = await TapLogin.Login();
- Debug.Log($"TapTap Login Complete accessToken: {accessToken.ToJson()}");
-}
-catch (Exception e)
-{
- if (e is TapException tapError) // using TapTap.Common
- {
- Debug.Log($"encounter exception:{tapError.code} message:{tapError.message}");
- if (tapError.code == TapErrorCode.ERROR_CODE_BIND_CANCEL) // Cancel Login
- {
- Debug.Log("Login cancelled");
- }
- }
-}
-
-// Fetch TapTap Profile to obtain basic user info, such as their nickname and avatar.
-var profile = await TapLogin.FetchProfile();
-Debug.Log($"TapTap Login Complete profile: {profile.ToJson()}");
-```
-
-```java
-// Instantiating Listener
-TapLoginHelper.TapLoginResultCallback loginCallback = new TapLoginHelper.TapLoginResultCallback() {
- @Override
- public void onLoginSuccess(AccessToken token) {
- Log.d(TAG, "TapTap authorization succeed");
- // Call TapLoginHelper.getCurrentProfile() to obtain basic user info, such as nickname and avatar.
- Profile profile = TapLoginHelper.getCurrentProfile();
- }
-
- @Override
- public void onLoginCancel() {
- Log.d(TAG, "TapTap authorization cancelled");
- }
-
- @Override
- public void onLoginError(AccountGlobalError globalError) {
- Log.d(TAG, "TapTap authorization failed. cause: " + globalError.getMessage());
- }
-};
-// Listener Registration
-TapLoginHelper.registerLoginCallback(loginCallback);
-// Login
-TapLoginHelper.startTapLogin(MainActivity.this, TapLoginHelper.SCOPE_PUBLIC_PROFILE);
-```
-
-```objectivec
-[TapLoginHelper registerLoginResultDelegate:delegator];
-if ([TapLoginHelper currentProfile]) {
- // Already logged in
-} else {
- [TapLoginHelper startTapLogin:@[@"public_profile"]];
-}
-
-// delegator
-- (void)onLoginCancel {
- // Login cancelled
-}
-
-- (void)onLoginError:(nonnull NSError *)error {
- // Login failed
-}
-
-- (void)onLoginSuccess:(nonnull TTSDKAccessToken *)token {
- // Login Complete
-}
-```
-
-
-
-
-
-This information is under fair use for developers.
-
-See [TapTap OAuth Interface](/sdk/taptap-login/guide/taptap-oauth/).
-
-## Check Login Status and User Info
-
-Login status and user info are saved in the local cache, which resets after logging in again. Logging out will clear the cache.
-
-
-
-```cs
-// Obtain login status
-try
-{
- var accesstoken = await TapLogin.GetAccessToken();
- Debug.Log("Logged in");
- // Enter game directly
-}
-catch (Exception e)
-{
- Debug.Log("Not logged in");
- // Start login
-}
-
-// Obtain user info
-await TapLogin.GetProfile();
-
-// Obtain real-time user info
-await TapLogin.FetchProfile();
-```
-
-```java
-// Obtain login status
-TapLoginHelper.getCurrentAccessToken();
-
-// Obtain user info
-TapLoginHelper.getCurrentProfile();
-
-// Obtain real-time user info
-TapLoginHelper.fetchProfileForCurrentAccessToken(new ApiCallback() {
- @Override
- public void onSuccess(Profile data) {
-
- }
-
- @Override
- public void onError(Throwable error) {
-
- }
-});
-```
-
-```objectivec
-// Obtain login status
-[TapLoginHelper currentAccessToken];
-
-// Obtain user info
-[TapLoginHelper currentProfile];
-
-// Obtain real-time user info
-[TapLoginHelper fetchProfileForCurrentAccessToken:^(TTSDKProfile *_Nonnull profile, NSError *_Nonnull error) {}];
-```
-
-
-
-## Log Out
-
-
-
-```cs
-TapLogin.Logout();
-```
-
-```java
-TapLoginHelper.logout();
-```
-
-```objectivec
-[TapLoginHelper logout];
-```
-
-
-
-## PC Login Configurations
-
-Unity SDK 3.5.2 onwards on Windows and macOS supports using a QR code or jump link for players to access the TapTap login page.
-
-SDK **supports QR scanning to log in by default**. Jump links require [additional configuration](/sdk/taptap-login/guide/start/#pc-login-configurations).
-
-## Upgrading to TDS Authentication
-
-As mentioned above, if preliminary development only requires TapTap Login as a third-party interface but requires TDS Authentication services later(or when upgrading older 1.x game versions to use 3.x services), this will incur a development fee. Details are as follows:
-
-1. Reference the above guide on [Initialization](#initialization) and [Quick Log-in With TapTap](/sdk/taptap-login/guide/start/#quick-log-in-with-taptap) to complete TapTap Login for TDS Authentication to obtain a TDSUser login.
-
-2. Obtain the authorized user's `Profile` info. Note: The `Profile` info here must be the same as the `Profile` previously obtained from the game. Game developers should be able to use this to find the persistent player data saved on the game server. Otherwise, they can bind the current TDSUser with the original player info. As for the game, developers can decide whether to bind TDSUser with player accounts:
-
- - TapSDK doesn't require binding because it saves the cached login status internally. [Get `currentUser`](/sdk/taptap-login/guide/start/#check-login-status) when necessary to retrieve the previous TDS login status.
- - Binding simplifies the process and allows you to expand TDS account info to more third-party platforms.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/guide/taptap-oauth.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/guide/taptap-oauth.mdx
deleted file mode 100644
index 3017a7bea..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/taptap-login/guide/taptap-oauth.mdx
+++ /dev/null
@@ -1,598 +0,0 @@
----
-title: TapTap OAuth Interface
-sidebar_position: 4
----
-
-import {Red, Blue, Black, Gray} from '/src/docComponents/doc';
-import {Conditional} from '/src/docComponents/conditional';
-
-## Summary
-
-An OAuth Mac Token is a string that the TapTap OpenAPI uses to validate the request.
-
-When using the [Basic TapTap User Verification Interface](/sdk/taptap-login/guide/tap-login/), an access token bundle is generated after the user grants permission to the current application. It can be transformed into a MAC Token string after encryption. This access token bundle remains valid until the user updates their account's security information or denies authorization. Developers are advised to manage MAC Tokens on their server as a means of subsequent communication with the TapTap server.
-
-For details on the MAC Token algorithm, see the [MAC Token Algorithm](#mac-token-algorithm) section of this document.
-
-
-
-The following interface is an example for domestic applications. However, when the mobile server is initialized to international regions, logins are registered as international. The following server document remains the same, except change `open.tapapis.cn` to `openapi.tap.io` as the international domain name.
-
-
-
-## Process
-
-1. When the client uses SDK's TapTap Login, it can [Obtain AccessTokens](/sdk/taptap-login/guide/tap-login/#check-login-status-and-user-info), including:
-
- ```java
- public String kid;
- public String access_token;
- public String token_type;
- public String mac_key;
- public String mac_algorithm;
- public String expire_in;
- private String json = null;
- ```
-
-2. Send the parameters obtained from the mobile client to the game server, then the server will sign a MAC Token.
-3. Request `https://open.tapapis.cn/account/profile/v1``https://openapi.tap.io/account/profile/v1` with `mac token` included in the header.
-
-Note: The returned `kid` and `access_token` values are equal. We recommend using `kid`.
-
-## API
-
-### Obtain Current Account Details
-
-> GET https://open.tapapis.cn/account/profile/v1?client_id=xxxhttps://openapi.tap.io/account/profile/v1?client_id=xxx Authorization mac token
-
-
-#### Request Parameters
-
-| String | Type | Description |
-| --------- | ------ | ------ |
-| client_id | string | The app's `Client ID` must be the same as the contract|
-
-#### Response Parameters
-
-String | Type | Description
---------------- | ------------- | ------------
-name | string | User name
-avatar | string | User avatar address
-gender | string | "female", "male" or empty string
-openid | string | The unique ID of the authorized user. The openid of each player for each game is different. The openid of players obtained by the same game is always the same
-unionid | string | Unique identifier of authorized users. Unionid is the same for a player in all games from a manufacturer. Therefore, manufactures have different unionids.
-
-#### Request Example
-
-Replace the `MAC ID` and `Client ID` with your own signed MAC Token and console's `Client ID`.
-
-
-
-```
-curl -s -H 'Authorization:MAC id="1/hC0vtMo7ke0Hkd-iI8-zcAwy7vKds9si93l7qBmNFxJkylWEOYEzGqa7k_9iw_bb3vizf-3CHc6U8hs-5a74bMFzkkz7qC2HdifBEHsW9wxOBn4OsF9vz4Cc6CWijkomnOHdwt8Km6TywOX5cxyQv0fnQQ9fEHbptkIJa
-gCd33eBXg76grKmKsIR-YUZd1oVHu0aZ6BR7tpYYsCLl-LM6ilf8LZpahxQ28n2c-y33d-20YRY5NW1SnR7BorFbd00ZP97N9kwDncoM1GvSZ7n90_0ZWj4a12x1rfAWLuKEimw1oMGl574L0wE5mGoshPa-CYASaQmBDo3Q69XbjTs
-KQ",ts="1618221750",nonce="adssd",mac="XWTPmq6A6LzgK8BbNDwj+kE4gzs="' "https://open.tapapis.cn/account/profile/v1?client_id="
-```
-
-
-
-
-
-```
-curl -s -H 'Authorization:MAC id="1/hC0vtMo7ke0Hkd-iI8-zcAwy7vKds9si93l7qBmNFxJkylWEOYEzGqa7k_9iw_bb3vizf-3CHc6U8hs-5a74bMFzkkz7qC2HdifBEHsW9wxOBn4OsF9vz4Cc6CWijkomnOHdwt8Km6TywOX5cxyQv0fnQQ9fEHbptkIJa
-gCd33eBXg76grKmKsIR-YUZd1oVHu0aZ6BR7tpYYsCLl-LM6ilf8LZpahxQ28n2c-y33d-20YRY5NW1SnR7BorFbd00ZP97N9kwDncoM1GvSZ7n90_0ZWj4a12x1rfAWLuKEimw1oMGl574L0wE5mGoshPa-CYASaQmBDo3Q69XbjTs
-KQ",ts="1618221750",nonce="adssd",mac="XWTPmq6A6LzgK8BbNDwj+kE4gzs="' "https://openapi.tap.io/account/profile/v1?client_id="
-```
-
-
-
-## Other
-
-### MAC Token Algorithm
-
-MAC Token contains the following strings:
-
-| String | Type | Description |
-| ------------- | ------ | ------------------------------- |
-| kid | string | mac_key id, The key identifier. |
-| access_token | string | This string currently has no use |
-| token_type | string | Token type (i.e. MAC) |
-| mac_key | string | MAC key |
-| mac_algorithm | string | MAC algorithm computation is called hmac-sha-1 |
-
-Use the MAC Token to sign an interface:
-
-### Script Request Example
-
-Use this script to verify the direct replacement parameters to ensure the MAC Token signed by your server is correct.
-
-Replace the CLIENT_ID with the `Client ID` obtained by the console. Replace ACCESS_TOKEN and MAC_KEY with the `access_token` and `mac_key` obtained after logging into the client:
-
-
-
-```
-#!/usr/bin/env bash
-
-# Client ID
-CLIENT_ID="Change to the `Client ID` obtained from the console"
-# Aaccess_token obtained by SDK
-ACCESS_TOKEN="1/hC0vtMo7ke0Hkd-iI8-zcAwy7vKds9si93l7qBmNFxJkylWEOYEzGqa7k_9iw_bb3vizf-3CHc6U8hs-5a74bMFzkkz7qC2HdifBEHsW9wxOBn4OsF9vz4Cc6CWijkomnOHdwt8Km6TywOX5cxyQv0fnQQ9fEHbptkIJagCd33eBXg76grKmKsIR-YUZd1oVHu0aZ6BR7tpYYsCLl-LM6ilf8LZpahxQ28n2c-y33d-20YRY5NW1SnR7BorFbd00ZP97N9kwDncoM1GvSZ7n90_0ZWj4a12x1rfAWLuKEimw1oMGl574L0wE5mGoshPa-CYASaQmBDo3Q69XbjTsKQ"
-# mac_key obtained by SDK
-MAC_KEY="mSUQNYUGRBPXyRyW"
-
-# Random number. Please replace for official launch.
-NONCE="8IBTHwOdqNKAWeKl7plt8g=="
-# Current timestamp
-TS=$(date +%s)
-
-# Request method
-METHOD="GET"
-# Request addres (contains query string)
-REQUEST_URI="/account/profile/v1?client_id=${CLIENT_ID}"
-# Request domain name
-REQUEST_HOST="open.tapapis.cn"
-
-MAC=$(printf "%s\n%s\n%s\n%s\n%s\n443\n\n" "${TS}" "${NONCE}" "${METHOD}" "${REQUEST_URI}" "${REQUEST_HOST}" | openssl dgst -binary -sha1 -hmac ${MAC_KEY} | base64)
-
-AUTHORIZATION=$(printf 'MAC id="%s",ts="%s",nonce="%s",mac="%s"' "${ACCESS_TOKEN}" "${TS}" "${NONCE}" "${MAC}")
-
-curl -s -H"Authorization:${AUTHORIZATION}" "https://${REQUEST_HOST}${REQUEST_URI}"
-```
-
-
-
-
-
-```
-#!/usr/bin/env bash
-
-# Client ID
-CLIENT_ID="Change to the `Client ID` obtained from the console"
-# Aaccess_token obtained by SDK
-ACCESS_TOKEN="1/hC0vtMo7ke0Hkd-iI8-zcAwy7vKds9si93l7qBmNFxJkylWEOYEzGqa7k_9iw_bb3vizf-3CHc6U8hs-5a74bMFzkkz7qC2HdifBEHsW9wxOBn4OsF9vz4Cc6CWijkomnOHdwt8Km6TywOX5cxyQv0fnQQ9fEHbptkIJagCd33eBXg76grKmKsIR-YUZd1oVHu0aZ6BR7tpYYsCLl-LM6ilf8LZpahxQ28n2c-y33d-20YRY5NW1SnR7BorFbd00ZP97N9kwDncoM1GvSZ7n90_0ZWj4a12x1rfAWLuKEimw1oMGl574L0wE5mGoshPa-CYASaQmBDo3Q69XbjTsKQ"
-# mac_key obtained by SDK
-MAC_KEY="mSUQNYUGRBPXyRyW"
-
-# Random number. Please replace for official launch.
-NONCE="8IBTHwOdqNKAWeKl7plt8g=="
-# Current timestamp
-TS=$(date +%s)
-
-# Request method
-METHOD="GET"
-# Request addres (contains query string)
-REQUEST_URI="/account/profile/v1?client_id=${CLIENT_ID}"
-# Request domain name
-REQUEST_HOST="openapi.tap.io"
-
-MAC=$(printf "%s\n%s\n%s\n%s\n%s\n443\n\n" "${TS}" "${NONCE}" "${METHOD}" "${REQUEST_URI}" "${REQUEST_HOST}" | openssl dgst -binary -sha1 -hmac ${MAC_KEY} | base64)
-
-AUTHORIZATION=$(printf 'MAC id="%s",ts="%s",nonce="%s",mac="%s"' "${ACCESS_TOKEN}" "${TS}" "${NONCE}" "${MAC}")
-
-curl -s -H"Authorization:${AUTHORIZATION}" "https://${REQUEST_HOST}${REQUEST_URI}"
-```
-
-
-
-### nodejs Request Example
-
-
-
-```javascript
-const http = require('http');
-const https = require('https');
-const crypto = require('crypto');
-
-function getAuthorization(requestUrl, method, keyId, macKey) {
- const url = new URL(requestUrl);
- const time = Math.floor(Date.now() / 1000).toString().padStart(10, '0');
- const randomStr = getRandomString(16);
- const host = url.hostname;
- const uri = url.pathname + url.search;
- const port = url.port || (url.protocol === 'https:' ? '443' : '80');
- const other = '';
- const sign = signData(mergeData(time, randomStr, method, uri, host, port, other), macKey);
-
- return `MAC id="${keyId}", ts="${time}", nonce="${randomStr}", mac="${sign}"`;
-}
-
-function getRandomString(length) {
- return crypto.randomBytes(length).toString('base64');
-}
-
-function mergeData(time, randomCode, httpType, uri, domain, port, other) {
- let prefix =
- `${time}\n${randomCode}\n${httpType}\n${uri}\n${domain}\n${port}\n`;
-
- if (!other) {
- prefix += '\n';
- } else {
- prefix += `${other}\n`;
- }
-
- return prefix;
-}
-
-function signData(signatureBaseString, key) {
- const hmac = crypto.createHmac('sha1', key);
- hmac.update(signatureBaseString);
- return hmac.digest('base64');
-}
-
-const client_id = "hskc**********kklm";
-const keyId = "1/VLDoiGUhNCIpUq827L**************zAJ-i8hT_w9vuPtPgdaPkWDv6K4eVe_yZnKz************EYep-T4ki5w3kyYACVnM61JJqDEKfpNnHoTZU********************iUArkgPsWEwOpZGxva7FnqbTwmpLT0a28UtiR5gyr4XXutbnE5tb4A-iSqRpqqtgABXBZd34U5Th3iJ1C666iYQFvuQL9uC-Zv7-xKCNjyPonBqU4ZWZnKLFf2mzprU5vJCA8q5by1SZxY63kZBQieHYxFjyOCQdJ-25gDlxiqDbNq08kmSdY6TB1qtQ68V37L6a8nIzyVHooX9uc2Yw";
-const macKey = 'VPDalRmxtBqi******************tH937GNKIvj3';
-const requestUrl = 'https://open.tapapis.cn/account/profile/v1?client_id='+ client_id ;
-const method = 'GET';
-
-
-const authorization = getAuthorization(requestUrl, method, keyId, macKey);
-console.log(authorization);
-
-const options = new URL(requestUrl);
-const client = options.protocol === 'https:' ? https : http;
-
-const req = client.request({
- hostname: options.hostname,
- port: options.port,
- path: options.pathname + options.search,
- method: 'GET',
- headers: {
- 'Authorization': authorization
- }
-}, (res) => {
- let data = '';
-
- res.on('data', (chunk) => {
- data += chunk;
- });
-
- res.on('end', () => {
- console.log(data);
- });
-});
-
-req.end();
-
-
-```
-
-
-
-
-
-```javascript
-const http = require('http');
-const https = require('https');
-const crypto = require('crypto');
-
-function getAuthorization(requestUrl, method, keyId, macKey) {
- const url = new URL(requestUrl);
- const time = Math.floor(Date.now() / 1000).toString().padStart(10, '0');
- const randomStr = getRandomString(16);
- const host = url.hostname;
- const uri = url.pathname + url.search;
- const port = url.port || (url.protocol === 'https:' ? '443' : '80');
- const other = '';
- const sign = signData(mergeData(time, randomStr, method, uri, host, port, other), macKey);
-
- return `MAC id="${keyId}", ts="${time}", nonce="${randomStr}", mac="${sign}"`;
-}
-
-function getRandomString(length) {
- return crypto.randomBytes(length).toString('base64');
-}
-
-function mergeData(time, randomCode, httpType, uri, domain, port, other) {
- let prefix =
- `${time}\n${randomCode}\n${httpType}\n${uri}\n${domain}\n${port}\n`;
-
- if (!other) {
- prefix += '\n';
- } else {
- prefix += `${other}\n`;
- }
-
- return prefix;
-}
-
-function signData(signatureBaseString, key) {
- const hmac = crypto.createHmac('sha1', key);
- hmac.update(signatureBaseString);
- return hmac.digest('base64');
-}
-
-const client_id = "5enu******wfy";
-const keyId = "1/JFZi8****IiumsGZI31iJH1q*****UKZ-eKA";
-const macKey = 'LMbNcKox*******kfmk7oWXbuRz';
-const requestUrl = 'https://openapi.tap.io/account/profile/v1?client_id='+ client_id ;
-const method = 'GET';
-
-const authorization = getAuthorization(requestUrl, method, keyId, macKey);
-console.log(authorization);
-
-const options = new URL(requestUrl);
-const client = options.protocol === 'https:' ? https : http;
-
-const req = client.request({
- hostname: options.hostname,
- port: options.port,
- path: options.pathname + options.search,
- method: 'GET',
- headers: {
- 'Authorization': authorization
- }
-}, (res) => {
- let data = '';
-
- res.on('data', (chunk) => {
- data += chunk;
- });
-
- res.on('end', () => {
- console.log(data);
- });
-});
-
-req.end();
-
-
-```
-
-
-
-### java Request Example
-
-
-
-```java
-package com.taptap;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.*;
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-public class Authorization {
- public static void main(String[] args) throws IOException {
- String client_id = "0RiAlMny7jiz086FaU";
- String kid = "1/hC0vtMo7ke0Hkd-iI8-zcAwy7vKds9si93l7qBmNFxJkylWEOYEzGqa7k_9iw_bb3vizf-3CHc6U8hs-5a74bMFzkkz7qC2HdifBEHsW9wxOBn4OsF9vz4Cc6CWijkomnOHdwt8Km6TywOX5cxyQv0fnQQ9fEHbptkIJagCd33eBXg76grKmKsIR-YUZd1oVHu0aZ6BR7tpYYsCLl-LM6ilf8LZpahxQ28n2c-y33d-20YRY5NW1SnR7BorFbd00ZP97N9kwDncoM1GvSZ7n90_0ZWj4a12x1rfAWLuKEimw1oMGl574L0wE5mGoshPa-CYASaQmBDo3Q69XbjTsKQ"; // kid
- String mac_key = "mSUQNYUGRBPXyRyW"; // mac_key
- String method = "GET";
- String request_url = "https://open.tapapis.cn/account/profile/v1?client_id=" + client_id; //
- String authorization = getAuthorization(request_url, method, kid, mac_key);
- System.out.println(authorization);
- URL url = new URL(request_url);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- // Http
- conn.setRequestProperty("Authorization", authorization);
- conn.setRequestMethod("GET");
- BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
- String line;
- StringBuilder result = new StringBuilder();
- while ((line = rd.readLine()) != null) {
- result.append(line);
- }
- rd.close();
- System.out.println(result.toString());
- }
- /**
- * @param request_url
- * @param method "GET" or "POST"
- * @param key_id key id by OAuth 2.0
- * @param mac_key mac key by OAuth 2.0
- * @return authorization string
- */
- public static String getAuthorization(String request_url, String method, String key_id, String
- mac_key) {
- try {
- URL url = new URL(request_url);
- String time = String.format(Locale.US, "%010d", System.currentTimeMillis() / 1000);
- String randomStr = getRandomString(16);
- String host = url.getHost();
- String uri = request_url.substring(request_url.lastIndexOf(host) + host.length());
- String port = "80";
- if (request_url.startsWith("https")) {
- port = "443";
- }
- String other = "";
- String sign = sign(mergeSign(time, randomStr, method, uri, host, port, other), mac_key);
- return "MAC " + getAuthorizationParam("id", key_id) + "," + getAuthorizationParam("ts", time)
- + "," + getAuthorizationParam("nonce", randomStr) + "," + getAuthorizationParam("mac",
- sign);
- } catch (MalformedURLException e) {
- e.printStackTrace();
- }
- return null;
- }
- private static String getRandomString(int length) {
- byte[] bytes = new byte[length];
- new SecureRandom().nextBytes(bytes);
- String base64String = Base64.getEncoder().encodeToString(bytes);
- return base64String;
- }
- private static String mergeSign(String time, String randomCode, String httpType, String uri,
- String domain, String port, String other) {
- if (time.isEmpty() || randomCode.isEmpty() || httpType.isEmpty() || domain.isEmpty() || port.isEmpty())
- {
- return null;
- }
- String prefix =
- time + "\n" + randomCode + "\n" + httpType + "\n" + uri + "\n" + domain + "\n" + port
- + "\n";
- if (other.isEmpty()) {
- prefix += "\n";
- } else {
- prefix += (other + "\n");
- }
- return prefix;
- }
- private static String sign(String signatureBaseString, String key) {
- try {
- SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
- Mac mac = Mac.getInstance("HmacSHA1");
- mac.init(signingKey);
- byte[] text = signatureBaseString.getBytes(StandardCharsets.UTF_8);
- byte[] signatureBytes = mac.doFinal(text);
- signatureBytes = Base64.getEncoder().encode(signatureBytes);
- return new String(signatureBytes, StandardCharsets.UTF_8);
- } catch (NoSuchAlgorithmException | InvalidKeyException e) {
- throw new IllegalStateException(e);
- }
- }
- private static String getAuthorizationParam(String key, String value) {
- if (key.isEmpty() || value.isEmpty()) {
- return null;
- }
- return key + "=" + "\"" + value + "\"";
- }
-}
-```
-
-
-
-
-
-```java
-package com.taptap;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.*;
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-public class Authorization {
- public static void main(String[] args) throws IOException {
- String client_id = "0RiAlMny7jiz086FaU";
- String kid = "1/hC0vtMo7ke0Hkd-iI8-zcAwy7vKds9si93l7qBmNFxJkylWEOYEzGqa7k_9iw_bb3vizf-3CHc6U8hs-5a74bMFzkkz7qC2HdifBEHsW9wxOBn4OsF9vz4Cc6CWijkomnOHdwt8Km6TywOX5cxyQv0fnQQ9fEHbptkIJagCd33eBXg76grKmKsIR-YUZd1oVHu0aZ6BR7tpYYsCLl-LM6ilf8LZpahxQ28n2c-y33d-20YRY5NW1SnR7BorFbd00ZP97N9kwDncoM1GvSZ7n90_0ZWj4a12x1rfAWLuKEimw1oMGl574L0wE5mGoshPa-CYASaQmBDo3Q69XbjTsKQ"; // kid
- String mac_key = "mSUQNYUGRBPXyRyW"; // mac_key
- String method = "GET";
- String request_url = "https://openapi.tap.io/account/profile/v1?client_id=" + client_id; //
- String authorization = getAuthorization(request_url, method, kid, mac_key);
- System.out.println(authorization);
- URL url = new URL(request_url);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- // Http
- conn.setRequestProperty("Authorization", authorization);
- conn.setRequestMethod("GET");
- BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
- String line;
- StringBuilder result = new StringBuilder();
- while ((line = rd.readLine()) != null) {
- result.append(line);
- }
- rd.close();
- System.out.println(result.toString());
- }
- /**
- * @param request_url
- * @param method "GET" or "POST"
- * @param key_id key id by OAuth 2.0
- * @param mac_key mac key by OAuth 2.0
- * @return authorization string
- */
- public static String getAuthorization(String request_url, String method, String key_id, String
- mac_key) {
- try {
- URL url = new URL(request_url);
- String time = String.format(Locale.US, "%010d", System.currentTimeMillis() / 1000);
- String randomStr = getRandomString(16);
- String host = url.getHost();
- String uri = request_url.substring(request_url.lastIndexOf(host) + host.length());
- String port = "80";
- if (request_url.startsWith("https")) {
- port = "443";
- }
- String other = "";
- String sign = sign(mergeSign(time, randomStr, method, uri, host, port, other), mac_key);
- return "MAC " + getAuthorizationParam("id", key_id) + "," + getAuthorizationParam("ts", time)
- + "," + getAuthorizationParam("nonce", randomStr) + "," + getAuthorizationParam("mac",
- sign);
- } catch (MalformedURLException e) {
- e.printStackTrace();
- }
- return null;
- }
- private static String getRandomString(int length) {
- byte[] bytes = new byte[length];
- new SecureRandom().nextBytes(bytes);
- String base64String = Base64.getEncoder().encodeToString(bytes);
- return base64String;
- }
- private static String mergeSign(String time, String randomCode, String httpType, String uri,
- String domain, String port, String other) {
- if (time.isEmpty() || randomCode.isEmpty() || httpType.isEmpty() || domain.isEmpty() || port.isEmpty())
- {
- return null;
- }
- String prefix =
- time + "\n" + randomCode + "\n" + httpType + "\n" + uri + "\n" + domain + "\n" + port
- + "\n";
- if (other.isEmpty()) {
- prefix += "\n";
- } else {
- prefix += (other + "\n");
- }
- return prefix;
- }
- private static String sign(String signatureBaseString, String key) {
- try {
- SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
- Mac mac = Mac.getInstance("HmacSHA1");
- mac.init(signingKey);
- byte[] text = signatureBaseString.getBytes(StandardCharsets.UTF_8);
- byte[] signatureBytes = mac.doFinal(text);
- signatureBytes = Base64.getEncoder().encode(signatureBytes);
- return new String(signatureBytes, StandardCharsets.UTF_8);
- } catch (NoSuchAlgorithmException | InvalidKeyException e) {
- throw new IllegalStateException(e);
- }
- }
- private static String getAuthorizationParam(String key, String value) {
- if (key.isEmpty() || value.isEmpty()) {
- return null;
- }
- return key + "=" + "\"" + value + "\"";
- }
-}
-```
-
-
-
-
-### General Interface Error Messages
-
-**Same format**
-
-| String | Type | Description |
-| ----------------- | ------ | ---------------------------------------------------- |
-| code | int | Reserved field for tracking problems in the future |
-| error | string | Error code used for assessing code logic |
-| error_description | string | Error description info, which is used to help understand and solve errors during development| |
-
-
-**Error Response**
-
-| Error code | Description |
-| ----------------------| ------------------------------------------------------------ |
-| invalid_request | The request is missing a required parameter, contains an unsupported parameter or parameter value, or the format is incorrect| |
-| invalid_time | Invalid ts time in the MAC Token algorithm. **Request to reconstruct the server time** |
-| invalid_client | Invalid client_id parameters |
-| access_denied | Authorized server rejects the request **this occurs when requesting user resources with a token. If this occurs, the client should log the user out and request the user to log in again.** |
-| forbidden | User does not have permission to perform this action. **Reauthenticating permission will not provide any help. This request should be not repeated.** |
-| not_found | Request failed. The requested resources were not found on the server. **Requests should not be repeated with the same parameters** |
-| server_error | The server error has occurred. **You may retry the request later, but there must be an upper limit (recommended: 3). If the first attempt fails, interrupt and inform the user** |
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/_category_.json
deleted file mode 100644
index c1f75ec93..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "礼包系统",
- "collapsed": true,
- "position": 9
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/faq.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/faq.mdx
deleted file mode 100644
index 584881d32..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/faq.mdx
+++ /dev/null
@@ -1,49 +0,0 @@
----
-title: FAQ
-sidebar_label: FAQ
-sidebar_position: 4
----
-
-### I’ve provided all the required parameters but I still got a 403 error.
-
-Please check if a special User Agent has been included in the request’s header.
-
-### With secondary checks enabled, does the game need to send two separate requests to the redemption system?
-
-No. With secondary checks enabled, the redemption system will send requests to the interface you configured on the Developer Center.
-
-### Unable to parse the content parameter of the serverless return value?
-
-The content in the return value is a nested JSON string. You can first read the string and then perform further JSON parsing. In addition, a new content_obj field has been added, which you can use selectively.
-
-### How to determine if the exchange is successful?
-
-You can judge by whether the error field in the return value is 0.
-
-### How to use the sign field in serverless exchange?
-
-Please refer to the [signature section](/sdk/tds-gift/guide/#signature) in the documentation.
-
-### Unity request for serverless exchange interface reports error HTTP/1.1 422 Unprocessable Entity
-
-Please check if the event switch is turned on and if the gift code has expired in Developer Console -> Gift Package Service.
-
-### Error when generating gift package, time cannot exceed 2040
-
-The current maximum gift package time is 2038-01-19 11:14:07 .
-
-### What's the difference between the three methods for redeeming gift codes?
-
-| Method | Interfaces neded | Features |
-| --------------- | ------------- |------------- |
-| Verified by game | Redeeming, secondary checks, and delivering items | You must maintain interfaces for both secondary checks and item delivery. Your game will check if conditions are met to deliver gifts. The logic for checking conditions and delivering gifts can be maintained separately. |
-| Requested by game | Redeeming and delivering items | You only need to maintain one interface for item delivery. The interface can be used to check conditions and deliver items. |
-| Redeeming without a server | Redeeming | Our service will check if the code is valid and the number of items remaining. The item information is returned as long as the code is valid. This provides a simpler workflow and you don't have to maintain your own interfaces. |
-
-### The gift redemption interface returns the exception {"error":100016,"message":"该礼包码无效码","info":{"dev_message":"invalid code","hint":"gift code is invalid"}}
-
-Check if the gift code passed into the interface is wrong. The gift code can be exported in **Developer Center > Your Game > Operational Tools > Gifts > Gift Event > Data > Export**. After clicking the `Export` button, a `.csv` file will be generated, and you can check the gift code in that file.
-
-### The `sent by game` gift redemption interface returns the exception {"error":100015,"message":"发送道具失败"}
-
-This exception indicates that the parameters passed when calling the interface are correct, and the signing is also correct, but the interface for sending items on the server side of the game may not be working. The game developer should check if there are any issues with the interface for sending items.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/features.mdx
deleted file mode 100644
index 7e5f6d673..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/features.mdx
+++ /dev/null
@@ -1,140 +0,0 @@
----
-title: Gift Packages Introduction
-sidebar_label: Introduction
-sidebar_position: 1
----
-
-## Overview
-
-Gift Packages lets you create gift codes for your game which players can redeem in your game later.
-
-### Capabilities
-
-- With Gift Packages, you can quickly add a gift system to your game without having to write the low-level logic for it.
-- Gift Packages comes with interfaces for redeeming gift codes, which players can access through your game or a website you build.
-
-### Highlights
-
-- You can create two types of gift codes with Gift Packages: unlimited codes and one-time codes. It’s up to you to decide which one to use for each event held in your game.
-- You can customize the characters and redeeming rules of your gift codes, as well as the items players would get by redeeming the codes.
-- Compared to setting up your own system, Gift Packages allows you to implement the same feature in your game with a lower cost spent on data storage. Gift Packages itself will also keep evolving to cater to the change in users’ requirements so you don’t have to do the work of constantly updating your system.
-- You can monitor the statistics data to observe how your gift codes have been consumed since they got launched and learn about the outcomes of the events you held for your game.
-
-## Basic Flow
-
-![](/img/tds-gift/gift01.png)
-
-## Using Gift Packages in Your Game
-
-### Accessing the Dashboard
-
-You can access the dashboard for managing gifts by going to **Developer Center**→**Game Services**→**Operational Tools**→**Gifts**
-
-### Creating/Editing an Event
-
-#### Setting up Servers
-
-- If you haven’t set up servers yet, please first complete this step by going to **Server Configuration**.
-- If you have already set up servers, you can select the servers you wish to use when creating gifts.
-
-![](https://capacity-files.lcfile.com/YV4PepRyA4HKVSdgGk0WuwN953Sq8r6n/io-gift02.png)
-
-#### Configuring the Basic Information of the Event
-
-- Gift Event Name
- - Give a name to the event (players will not see it).
-- Valid Date
- - Players can only redeem your gift codes during the dates you provided.
-- Gift Code Type
- - Unlimited Code: The same code can be redeemed by unlimited users. The code can be no longer than 13 digits and can only contain numbers and letters.
- - One-time Code: The same code can only be redeemed by one user.
-- Gift Code Generation Rules
- - Generate Randomly: The system will generate random code(s) with 13 digits.
- - Enter prefix (for one-time code only): You can specify a prefix for the codes being generated. To ensure that enough codes can be generated, please keep the length of the prefix within 6 digits.
-- Custom redeeming rules
- - Besides the basic redeeming rules, you can set up a maximum of 10 custom redeeming rules.
- - Condition: The name of the condition (like season or registration time); Comparison operator: Could be equal to, less than, less than or equal to, greater than, or greater than or equal to; Value: The value to compare against.
- - For example, to only allow users registered in 2022 to claim your gift, you can do: Registration time (Condition) is equal to (Comparison operator) 2022 (Value).
-- Items and Amount
- - Item: The name of the item given to players.
- - Amount: The amount of the item given to players.
-
-:::tip
-
-**Attention**: Don’t fit multiple kinds of items into the same row. You can add no more than 10 kinds of items in total.
-
-:::
-
-#### Launching the Event
-
-To launch the event you created and make its codes effective, please turn on the toggle of the event under **Gift Event**.
-
-![](https://capacity-files.lcfile.com/zzozCSJXR5mfuOEXb2eVm865hMCLM6Kk/io-gift03.png)
-
-> **Attention**: Only the codes of launched events can be redeemed. Please make sure to turn on the toggle of your event when you are ready to start the event.
-
-#### Editing the Event
-
-Click on **Edit** to edit the event. You can only edit the Servers, Gift Event Name, and Valid Date of the event.
-
-### Adding Codes/Counts
-
-**Adding Codes**
-
-To add codes, click on **Add Codes**, enter the number of new codes, and click on **Generate And Export**. The new codes will be automatically downloaded to your computer.
-
-**Adding Counts**
-
-To add counts, click on **Add Counts**, enter the number of counts to be added, and click on **Confirm**. The same code used for the event will be downloaded to your computer.
-
-:::tip
-
-**Attention**: Adding counts for an event with unlimited codes will not produce new gift codes. To obtain new gift codes, create a new event instead.
-
-:::
-
-### Viewing History
-
-You can view the history of generating gift codes by going to **Data**.
-
-![](https://capacity-files.lcfile.com/Bc2dE6AOsxqbuH6BzWhnGLM8QQvc7sTe/io-gift04.png)
-
-### Server Configuration
-
-- Server Name
- - The name of the server
-- Server code
- - URL for receiving notification about distributed items
-- URL for receiving notification about distributed items
- - check_url
-- check_url
- - Optional; used for secondary check of Gift Code Generation Rules
-
-### Data Lookup
-
-**Single CDKEY or single user**
-
-- Enter a **CDKEY** to get the status of the corresponding gift code
-- Enter a **uid** to view the redemption history of the corresponding user
-
-![](https://capacity-files.lcfile.com/iwMdA4CclNiT1c5VKAHVlIJeEXJOzOAi/io-gift05.png)
-
-**Gift event**
-
-- Where to find: The homepage of Gifts
-- Use any of the following filters to look up the statistics of gift events:
- - Code Type
- - Status
- - Gift event ID or name
-
-![](https://capacity-files.lcfile.com/VwQ8oSydEB4R8oYaRPbTN21bBgpPGKYu/io-gift06.png)
-
-## FAQ
-
-Q: I see an error saying that the system cannot save the Client ID.
-
-A: Please go to **Developer Center**→**Game Services**→**Configuration** and enable the service to obtain a Client ID.
-
-Q: Why can’t I see the **Game Services** tab?
-
-A: When the administrator of a provider adds a new user to the provider, the administrator needs to grant the user the permission to view a game’s configuration before the user can see the **Game Services** tab and the content within it.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/guide.mdx
deleted file mode 100644
index 2adef0517..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/guide.mdx
+++ /dev/null
@@ -1,617 +0,0 @@
----
-title: Gift Packages Guide
-sidebar_label: Guide
-sidebar_position: 2
----
-
-:::tip
-Before integrating Gift Packages into your game, please first complete setting up your game. You can obtain the **Client ID and Server Secret** of your game on **Game Services - Configuration**. This information should be kept private and shall not be shared with anyone else.
-:::
-
-## Getting Started
-
-You can only use Gift Packages with your game if your game is capable of sending requests to our server. At this time, players cannot redeem gift codes without connecting to the internet.
-
-You can use one of the following methods to complete the process of redeeming a gift code.
-
-| Method | API | The game needs to verify that the player meets the conditions of claiming the gifts | The game needs to provide an interface for delivering items to the player |
-| -------------- | -------------------------------- | :----------------------------- | :----------------------- |
-| Verified by game | /api/v1.0/cdk/game/submit-check | ✓ | ✓ |
-| Requested by game | /api/v1.0/cdk/game/submit-send | ✗ | ✓ |
-| Redeeming without a server | /api/v1.0/cdk/game/submit-simple | ✗ | ✗ |
-| Verified by game (used on a web page) | /api/v1.0/cdk/page/submit-check | ✓ | ✓ |
-| Requested by game (used on a web page) | /api/v1.0/cdk/page/submit-send | ✗ | ✓ |
-
-### Steps to Integrate Gift Packages Into Your Game
-
-- Finish setting up the game and obtain the **Client ID and Server Secret**.
-- Optional: Go to **Operational Tools - Gifts - Server Configuration** and add a server that will be used to receive notifications and deliver items to players.
-- Create gift events and export gift codes. Once you finish this step, you will be ready to test the gift codes.
-- Players redeem your gift codes and obtain the items.
-
-### Steps to Redeem a Gift Code
-
-Below are the basic steps for redeeming a gift code:
-
-1. Send the **gift code** and **configurations** to the interface for redeeming gift codes.
-2. The interface will check if the information provided is valid.
-3. The system will determine if **the game’s interface for secondary checks** needs to be invoked. (Optional; the interface is used if it is configured)
-4. If all the checks have passed, the system will determine if **the game’s interface for delivering items to players** needs to be invoked. (Optional; the interface is used if it is configured)
-
-There are three different types of interfaces for redeeming a gift code. You can integrate one of them into your game:
-
-| Name | Description |
-| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Verified by game | Choose this flow if you plan to set up a server that connects to our system, are able to have your secrets securely stored, and wish to have your app verify if a request for redeeming a code should be allowed while having an interface provided by your game invoked to deliver the items to the player. |
-| Requested by game | Choose this flow if you plan to set up a server that connects to our system, are able to have your secrets securely stored, and wish to have an interface provided by your game invoked to deliver the items to the player. |
-| Redeeming without a server | Choose this flow if you don’t want to build and maintain your own interface. Your game will be invoking an interface provided by our system and a value will be returned indicating whether the request succeeded. |
-
-### Three Ways for Redeeming
-
-#### Verified by Game
-
-When your game sends a request to the `submit-check` interface for redeeming a gift code, the system will check the inventory as well as whether the submitted data is valid. If everything looks good, the system will invoke the URL you provided for secondary checks with the conditions you set up on the Developer Center as arguments.
-If your interface approves the request, the system will invoke your interface for delivering items to the player with the items configured on the Developer Center.
-![Verified by game](/img/tds-gift/submit_check.png)
-
-#### Requested by Game
-
-When your game sends a request to the `submit-send` interface for redeeming a gift code, the system will check the inventory as well as whether the submitted data is valid. If everything looks good, the system will invoke your interface for delivering items to the player with the items configured on the Developer Center.
-![Requested by game](/img/tds-gift/submit_send.png)
-
-#### Redeeming Without a Server
-
-When your game sends a request to the `submit-simple` interface for redeeming a gift code, the system will check the inventory as well as whether the submitted data is valid. If everything looks good, the system will return the items configured on the Developer Center to you.
-![Redeeming without a server](/img/tds-gift/submit_simple.png)
-
-## Connecting to the Interface for Redeeming Items
-
-### Implementing Secondary Checks With Your Game
-
-If you have set up custom redeeming rules on the Developer Center and you’re redeeming a gift code with an interface that has the capability of performing secondary checks, the system will send the redeeming rules to your game for a secondary check. The configuration shown in the image below will trigger the following request to the interface you set up on the Developer Center:
-
-![Custom redeeming rules](/img/tds-gift/custom_redemption.png)
-
-```
-curl --location -g --request POST \
---header 'Content-Type: application/json' \
---data-raw '{"activity_id":,"character_id":,"content":"[{\"condition\":\"level\",\"operate":\"gt\",\"value\":10}]","ext":,"gift_code":,"nonce_str":"abcdc","server_code": ,"sign":"42d2b58a8cd58b5e63d90524aaf63ef97e04f223","timestamp":1658825322}'
-```
-
-After verifying the request, please return a JSON containing the following fields to inform the system of the result.
-
-```json
-{
- "status": true,
- "errorCode": "An error code string"
-}
-```
-
-You can use snake_case as well if you wish to maintain consistency.
-
-```json
-{
- "status": true,
- "error_code": "An error code string"
-}
-```
-
-If you don’t need our system to know the specific error message, your interface can simply return a **non-200 HTTP code** to indicate that the redemption failed, or an **HTTP code 200** to indicate that the redemption succeeded.
-This allows your interface to respond to requests without following the format specified above.
-
-| Request parameter | Type | Description |
-| ----------------- | -------------- | -------------------- |
-| activity_id | String | Event ID |
-| character_id | String | User ID |
-| content | String (JSON) | Custom redeeming rules |
-| content.condition | String | Custom redeeming rules (condition) |
-| content.operate | String | Custom redeeming rules (comparison operator) |
-| content.value | String | Custom redeeming rules (value) |
-| ext | String | A value provided when sending a request to the interface |
-| gift_code | String | The gift code entered by the user |
-| nonce_str | String | A random string used for this request |
-| server_code | String | Server code |
-| sign | String | Signature |
-| timestamp | Number | Timestamp with a second precision |
-
-| Response parameter | Type | Description |
-| --------- | -------------- | ----------------------------------------------- |
-| errorCode | String | Error type; same as `error_code`; if both of them exist, `errorCode` will be used |
-| ext | String (optional) | A string passed to the interface for delivering items to the player |
-| status | Boolean | Whether the request passed the verification; `true` means yes and `false` means no |
-
-### Delivering Items to Players
-
-After validation is completed, our system will send a request to **the game’s interface for delivering items to players** with the items configured on the Developer Center as well as the configured parameters. The configuration shown in the image below will trigger the following request to the interface you set up on the Developer Center:
-![Custom redeeming rules](/img/tds-gift/custom_prizes.png)
-
-```
-curl --location -g --request POST \
---header 'Content-Type: application/json' \
---data-raw '{"activity_id": ,"character_id":"uid","content":"[{\"name\": \"ITEM_NAME\", \"number\": 2}]","ext":,"gift_code":"gift_code","nonce_str":"abcdc","server_code":,"sign":"fbe23e6f6f5193355b29e9ea2913b1992af56346","check_ext":,"timestamp":1658827492}'
-```
-
-After completing the request, please return a JSON containing the following fields to inform the system of the result. You can use snake_case as well if you wish to maintain consistency.
-
-```json
-{
- "status": true,
- "errorCode": "An error code string"
-}
-```
-
-If you don’t need our system to know the specific error message, your interface can simply return a **non-200 HTTP code** to indicate that the redemption failed, or an **HTTP code 200** to indicate that the redemption succeeded.
-This allows your interface to respond to requests without following the format specified above.
-
-| Request parameter | Type | Description |
-| -------------- | -------------- | ---------------------------- |
-| activity_id | String | Event ID |
-| character_id | String | User ID |
-| server_code | String | Server code |
-| content | String (JSON) | Custom items set up on the Developer Center |
-| content.name | String | Item name |
-| content.number | Number | Item count |
-| ext | String | A value provided when sending a request to the interface |
-| gift_code | String | The gift code entered by the user |
-| nonce_str | String | A random string used for this request |
-| sign | String | Signature |
-| check_ext | String | The string provided by the interface that does secondary checks |
-| timestamp | Number | Timestamp with a second precision |
-
-| Response parameter | Type | Description |
-| --------- | ------ | ----------------------------------------------- |
-| errorCode | String | Error type; same as `error_code`; if both of them exist, `errorCode` will be used |
-| status | Boolean | Whether the request succeeded; `true` means yes and `false` means no |
-
-### Redeeming Without a Server
-
-With this method, you don’t have to implement an interface for validating requests and delivering items yourself.
-
-If you choose to use this method, make sure to **specify that the event doesn’t need a server when creating the event on the Developer Center**.
-
-Our system will provide the redemption result upon receiving a request. Your game will then determine whether to give the items to the player depending on the result.
-
-### Three Item Delivery Interfaces
-
-#### With Secondary Check
-
-POST /api/v1.0/cdk/game/submit-check
-
-Header Content-Type:application/json
-
-Request parameters:
-
-| Request parameter | Type | Required | Description |
-| ------------ | ------ | -------- | ------------------------------------------------- |
-| client_id | String | Yes | The Client ID of the game |
-| gift_code | String | Yes | The gift code entered by the user |
-| server_code | String | Yes | Server code |
-| character_id | String | Yes | User ID |
-| nonce_str | String | Yes | A random string used for this request; it’s recommended to use 5 digits |
-| sign | String | Yes | Signature |
-| timestamp | Number | Yes | Timestamp with a second precision; the request will only be accepted if the timestamp is within 1 minute |
-| ext | String | No | This field will be passed to the game’s interface for secondary checks |
-
-Example request:
-
-```
-curl --location --request POST '/api/v1.0/cdk/game/submit-check' \
---header 'Content-Type: application/json' \
---data-raw '{"client_id":,"gift_code":,"server_code":,"character_id":,"nonce_str":,"sign":,"timestamp":,"ext": }'
-```
-
-#### Without Secondary Check
-
-POST /api/v1.0/cdk/game/submit-send
-
-Header Content-Type:application/json
-
-Request parameters:
-
-| Request parameter | Type | Required | Description |
-| ------------ | ------ | -------- | -------------------------------- |
-| client_id | String | Yes | The Client ID of the game |
-| gift_code | String | Yes | The gift code entered by the user |
-| server_code | String | Yes | Server code |
-| character_id | String | Yes | User ID |
-| nonce_str | String | Yes | A random string used for this request; it’s recommended to use 5 digits |
-| sign | String | Yes | Signature |
-| timestamp | Number | Yes | Timestamp with a second precision; the request will only be accepted if the timestamp is within 1 minute |
-
-Example request:
-
-```
-curl --location --request POST '/api/v1.0/cdk/game/submit-send' \
---header 'Content-Type: application/json' \
---data-raw '{"client_id":,"gift_code":,"server_code":,"character_id":,"nonce_str":,"sign":,"timestamp":,"ext": }'
-```
-
-#### Redeeming Without a Server
-
-POST /api/v1.0/cdk/game/submit-simple
-
-Header Content-Type:application/json
-
-Request parameters:
-
-| Request parameter | Type | Required | Description |
-| ------------ | ------ | -------- | ----------------------------------- |
-| client_id | String | Yes | The Client ID of the game |
-| gift_code | String | Yes | The gift code entered by the user |
-| character_id | String | Yes | User ID |
-| nonce_str | String | Yes | A random string used for this request; it’s recommended to use 5 digits |
-| sign | String | Yes | Signature; use the Client ID instead of the Secret for generating this signature |
-
-Response parameters:
-
-| Response parameter | Type | Description |
-| -------------- | -------------- | -------------------------------- |
-| activity_id | String | Event ID |
-| nonce_str | String | A random string |
-| timestamp | String | Timestamp |
-| content | String (JSON) | Custom items configured on the Developer Center |
-| content.name | String | Item name |
-| content.number | Number | Item count |
-| sign | String | Returned signature used to check if the returned data has been tampered |
-| success | Boolean | Whether the request succeeded |
-
-Example request:
-
-```
-curl --location --request POST '/api/v1.0/cdk/game/submit-simple' \
---header 'Content-Type: application/json' \
---data-raw '{"client_id":,"gift_code":,"character_id":,"nonce_str":,"sign":,"timestamp":}'
-```
-
-Shell Request example:
-
-```
-#! /usr/bin/env bash
-
-# TapDC Background application Client ID
-client_id="Replace with console's `Client ID`"
-# The conversion code of this exchange can be found in the "Package Activity panel - Data - Export" and then the exported file in the format of S0LLC8ICB2MP2
-gift_code="Replace with the redemption code in the package panel"
-
-# A random character string. Five random characters are recommended
-nonce_str="A2B3Z"
-# Game user ID
-character_id="6347de128b****3ee825e029"
-# Signature, which replaces Secret with ClientId
-signed=$(echo -n $(date +%s)$nonce_str$client_id |openssl sha1)
-RESULT_REQUEST=`curl --location --request POST 'https://poster-api.xd.cn/api/v1.0/cdk/game/submit-simple' \
---header 'Content-Type: application/json' \
---data-raw "{\"client_id\":\"$client_id\",\"gift_code\":\"$gift_code\",\"character_id\":\"$character_id\",\"nonce_str\":\"$nonce_str\",\"sign\":\"$signed\",\"timestamp\":$(date +%s)}"`
-
-echo $RESULT_REQUEST
-```
-
-
-
-
-Android Request example:
-
-available for reference[ Android Demo ](https://github.com/taptap/TapSDK-Android-Demo/blob/main/app/src/main/java/com/tds/demo/fragment/GiftFragment.java) Gift package system function
-
-
-
-
-C# Request example:
-
-```
-using UnityEngine.Networking;
-using System;
-using System.Text;
-using System.Security.Cryptography;
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
-
-public class APIClient : MonoBehaviour
-{
- private const string apiUrl = "https://poster-api.xd.cn/api/v1.0/cdk/game/submit-simple";
- private const string contentType = "application/json";
- private const string clientId = "hskcocvse6x1cgkklm";
- private const string giftCode = "NZ4mp2cztRMXH";
- private const string characterId = "879a0cdd9nnb917055ee";
- private const string nonceStr = "RFG7U";
-
- public GUISkin demoSkin;
-
-
- void Start()
- {
-
- }
-
-
- private void OnGUI()
- {
-
- GUI.skin = demoSkin;
- float scale = 1.0f;
-
-
- float btnWidth= Screen.width / 5 * 2;
- float btnWidth2 = btnWidth + 80 * scale;
-
- float btnHeight = Screen.height / 25;
- float btnTop = 30 * scale;
- float btnGap = 20 * scale;
-
- GUI.skin.button.fontSize = Convert.ToInt32(13 * scale);
-
- var style = new GUIStyle(GUI.skin.button) { fontSize = 20 };
- var labelStyle = new GUIStyle(GUI.skin.label) { fontSize = 30 };
-
- if (GUI.Button(new Rect((Screen.width - btnGap) / 2 - btnWidth, btnTop, btnWidth /2, btnHeight), "返回", style))
- {
- UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(0);
-
- }
-
- btnTop += btnHeight + 20 * scale;
-
- if (GUI.Button(new Rect((Screen.width - btnGap) / 2 - btnWidth, btnTop, btnWidth, btnHeight), "无服务器兑换", style))
- {
- StartCoroutine(StartRequest());
- }
-
-
- }
-
- private IEnumerator StartRequest()
- {
- // Get the current timestamp (seconds)
- int timestamp = (int)(System.DateTime.UtcNow.Subtract(new System.DateTime(1970, 1, 1))).TotalSeconds;
-
- // Concatenate and encrypt the sign parameter
- string sign = GetSign(timestamp, nonceStr, clientId);
-
- // Build request parameter
- string requestBody = "{\"client_id\":\"" + clientId + "\",\"gift_code\":\"" + giftCode + "\",\"character_id\":\"" + characterId + "\",\"nonce_str\":\"" + nonceStr + "\",\"timestamp\":" + timestamp + ",\"sign\":\"" + sign + "\"}";
-
- // Create the UnityWebRequest object
- UnityWebRequest request = new UnityWebRequest(apiUrl, "POST");
-
- // Set request header
- request.SetRequestHeader("Content-Type", contentType);
-
- // Set request body
- byte[] bodyRaw = Encoding.UTF8.GetBytes(requestBody);
- request.uploadHandler = new UploadHandlerRaw(bodyRaw);
- request.downloadHandler = new DownloadHandlerBuffer();
-
- // send request
- yield return request.SendWebRequest();
-
- // Processing response
- if (request.result == UnityWebRequest.Result.Success)
- {
- Debug.Log("API request succeeded");
- Debug.Log(request.downloadHandler.text);
- }
- else
- {
-
- Debug.Log("API request failed: " + request.error);
- Debug.Log(request.downloadHandler.text);
-
- }
- }
-
- private string GetSign(int timestamp, string nonceStr, string clientId)
- {
- // Concatenate parameters and perform SHA1 encryption
- string signString = timestamp.ToString() + nonceStr + clientId;
- byte[] signBytes = Encoding.UTF8.GetBytes(signString);
- byte[] signHash = new SHA1CryptoServiceProvider().ComputeHash(signBytes);
- string sign = BitConverter.ToString(signHash).Replace("-", "").ToLowerInvariant();
-
- return sign;
- }
-}
-
-
-```
-
-
-
-
-Example response:
-
-```json
-{
- "activity_id": "TDS20220928151122U9H",
- "content": "[{\"name\": \"ITEM\", \"number\": 2}]",
- "nonce_str": "0DD4B",
- "sign": "5895f87d3dfb5e0918c1b195c015d9284609d122",
- "timestamp": 1664354023,
- "success": true
-}
-```
-
-#### Response
-
-If succeeded:
-
-```json
-{
- "success": true,
- "messages": "Success"
-}
-```
-
-If failed:
-
-| Response parameter | Description |
-| ---------------- | ------------ |
-| error | Error code |
-| message | Summary of the error |
-| info | Error detail |
-| info.dev_message | Brief instructions for the developer |
-| info.hint | Detailed instructions |
-
-```json
-{
- "error": 100001,
- "message": "Invalid input",
- "info": {
- "dev_message": "",
- "hint": "test error"
- }
-}
-```
-
-### Request Domain
-
-| TapTap edition | Domain |
-| --------------- | ------------------------- |
-| China (taptap.cn) | https://poster-api.xd.cn |
-| Other countries and regions (taptap.io) | https://poster-api.xd.com |
-
-### Signature
-
-For security reasons, when your game communicates with our system, it needs to provide a signature in each request. **The “Secret” mentioned below refers to the Server Secret found on Game Services > Configuration on the Developer Center**.
-
-:::tip
-To ensure the highest security, please never use the Server Secret of your game on the client side.
-:::
-
-Pseudo-code for obtaining a signature:
-
-```
-sign == sha1(timestamp + nonce_str + secret)
-```
-
-You can check if the process of obtaining signatures is implemented correctly with the help of unit testing. The code below is an example in golang:
-
-```go
-func TestMakeSign(t *testing.T) {
- timestamp := 1655724586
- nonceStr := "abcde"
- secret := "abc"
- sign := MakeSign(int64(timestamp), nonceStr, secret)
- assert.Equal(t, sign, "3cb8c38833fa742e7873378faddcbe5b56088482")
- //output: 3cb8c38833fa742e7873378faddcbe5b56088482
-}
-```
-
-To ensure that the Secret never appears on the client side, when redeeming without a server, the signature can be generated **with the Client ID instead of the Secret**.
-
-```
-sign == sha1(timestamp + nonce_str + client_id)
-```
-
-### Using the Redemption Interface on a Web Page
-
-Our system provides a simple interface for redeeming gift codes that supports CAPTCHA. If you have a web page for your event, you can embed this interface into the web page. This interface shares the same flow as those introduced earlier. The only difference is that some of the parameters have been changed.
-
-#### Getting a CAPTCHA
-
-:::tip
-Each CAPTCHA is only valid for 5 minutes. You can request a new CAPTCHA if one expires.
-:::
-
-GET /api/v1.0/cdk/page/captcha-img
-
-Header Content-Type:application/json
-
-| Response parameter | Description |
-| -------- | -------------------------------- |
-| img | The CAPTCHA image in base64 |
-| key | The key of the CAPTCHA; needs to be submitted together with the user input |
-
-Example response:
-
-```json
-{
- "img": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAAA8CAYAAAAjW/WRAAANc0lEQVR4nOydCXAUV3qA/z7mHs2t0bWSkJCEECCO5Ta2w5GFmMCGrGHNemuXXZwYluxhQrEVkrWzIkUS29hlOxgfYFypJBATbONgG7ABGxwOg7GQQAJJCB0gCWkOzT3T09Od6haSR6PRXJpL0vuqKKZfv379uvt9+t97/UYihdWf/AEQCERQ8FRXAIFIZ5AgCEQIkCAIRAiQIAhECJAgCEQIkCAIRAiQIAhECJAgCEQIkCAIRAjIVFcgnTAUaqpSXQcOXavp2VTXAdHHuBMkXSQIRag6InmSy5gVZDSIEAvBrgtJkzjGjCBjVYhIQNIkjlEpSKJkSJdGFY/rCywjXa5ttIGNluXu8ZJitDcUdB+SS9oKMtKGMJ4aQDykGU/3KxrSSpCRPOgx+YBZwHLqjeqcelO2ssupETm80uofTrzcNUltDndoOt5LpdWare61lEvdrmLSS+twlpWwADQlFN4zqtUXujN1txJx3uPz1w17Lw7985Ihae8u3jRw/SkVJNaHOCZl8OPx7efWYwyLszjG2LVio6lA0WXOk5szDC75xPMd3z9atfAQJSXpaMpM5b1WWa05uZ1dj3HtzSaX19rksrsOqdRIkyQl8nhkOpO5XGs2L7VmyC/fKSg4NdLzhRICgkjhL0QgSR+kIynC4xWTLtzHkAIXLXVoxBbAAPJuGPKVnU49J46iyyE1FCut0ZQZeP8ifQ7++WJ5BlqTqSjLYFjcmaU/aVap2gP3u8Vi293cnMu9SsXtiS2tT2caja09Wm1TqDI3nHljoE5P/N3psHVYcfG9mNtOUgSJRYrxJEQg3SXKNhbDWMCA1Tf1FiruO3N6ipVNGMMQDo3YSEkFUUWPYMQiTCyy6A3GR4xq9flgcvhjl8lMZqXyrNZknr+y5vDPhssXToiRyBAM7Ml1k9nhdh7esTvmkyEpYkfXbFEUX+qa4FSLHPnVPSViGyW36aWGa39eXG0oUlhZHBv2mcWDaJ9dqOc2/UbdDqNaffJeTta3ABirNxhKVRbrzAynrQJnfVBgawC518Ln9RBiaFJOhymmS/x2oqNDJAwag6zd9bcR35hg8sTzxiL6WLzn2kMZ3c7Mrsma5q+fmFST7PPH+kz7u0Gdsgkg9LnAKtSCm5QChYsH8hbabkKXtADKzVf5ccCxRT+p+uOr22Hnr3YFLTvRMgQj4kF6NPLsffPAsPuQFNHBCSI3uLQdU7RN3zxeWpfKuvjLsu3d4I04EK6f2JZRBr2iTGABGJdEUmdUq7555y9/83O3WASv/OMm+MMzL/J5W0oL4H+fWAG/3vkWv50KIQIZdgxSfP9qVlnHhTKZx6JgMJzp/uGMD6+UrLrhEKk8EHCzNj/9i0HHBm77sxagaiRdt3BgHpK/Jlbg8wHOsuDDccxDEIRVJMJtQhFVajIm6twJgWUxWkhQMqNLkczT+g+E+9kWIv+LG3YMu+/05DVwdcF0mHnxGq4026YCAPcPGqaWwKwLfUGRk6HsdvOP3CJR54qL752Py0XEgSERRGu7K3/s6r+t1lnbygMzC4USWPHo38C0SUvDFuwfKUbadYsKBsN4MYJAGCUS8bdZhY5lLTdHdI4k8sjbtfNENq/UkiszxruLFUyC4Qg1FRrYDbuSsyxkWZwMOpOpKNNgfLSxuOi/aJKk5A6Hpqi1bUPdpLLXfAThjbReiWaQIApnj2T9uX/4Kyll03kEst6zFT858umcP91YXXcCTp/fz+fBcQKe2fjfIBbJhxQWS/cp7vI8EERYr8tUHaxYjzkEGm+hpcb0myvvc7vV+2Ysd1d217vmdrRFW9dUMPfgrUpVhz2TITDf57+b9UU0x0YjAISRYDjCvXPgmN35+aDtoy0dcPLufTiqz37eLRLZubTyxqYnzSrllfuZmQl5WRgrg7pYq668vJqTg8Vw36/X7lZVaSZs5NIXzFoLnd0NUN90DgSkCAh8cM9sJOOKUI0+UJ5QMg2U8yB6ZBwr+QHmJFWOZS1HOGnILrmczrbbvbm2TtENXeloEcSlFDp1LT4BsCAM3JcMAQIJJ4T/uCHYAP/V603QbnfCC/MrYQ9JbOfSDt1uh5cF5MV0kwP8I0hl66mipTX7+cHDjIrlsHLJM4MyUl4X1DeehbzsctBpClM+2B5OFm5QiAELF41nQCM8C2rhV3B22+p/0r4895c9z371hvTLglLx9czJpi3ffJT8WkcHJwBmEABRJwegMaD/xBTyS9LxEMCfSKJDpANpTpY365qhy+WGv581GUgMA5plYc/1JtCKRfDT0oKBvKluW/7wgnCV/+DELqhrPMsn/nTNv0Jh3vQhmdOp4sOBm8XiH+3dsuOcoRrK5M8CidvASU+AFudv4WHdDLB6Z8Bd18+gQrGVz5/ICYNICBcFMDsBxGUlL8a+59Y9x9ufIKKJDlGCldy5s1LgpTVfLJtbQmAYmD0UvHCtAdYU5cKcTE3Qg9KhvfXN+LAMNLZ8zSdgGAZ52ZP5zxZbN6y6dn6fXax2tmVOM6S2qpFBWERirtFnb4Wq6qmbPnHN7ryl2TtrC2AgOLxj93Mrq16volnVQP5wY6CRCjTSbpDARZMrL3292iMROOVGl8iuk7hHUp9+4hkdwlHYfneRyEPl3SqZeCCrzUxxaRW3Gp4SUVTB5W7ToLw4hsEUtQJeWjh9xMtc4gEviNHcDl5v332XSdVw8/ZXcPDLN7ulHot+OcBTXLpDpLx/ZtqGDxpz5nWkoqKRgnkJgvufxYBlZF6X/ETxQ5gPE/oyqG4u/fjmbf+i2TN1w+Edu18Pdny0455EjwO8EpKmZCQnh1bTblPEKkgCo0NYVFbrwi595oc0SVL9aXWTyvb55/GX4Y265iFl9O9Ptii8IHbndxbbHSY4evJ5EBLCDLM857aYsqsklE0r81iy/uzq67+g5wjfvqOf2Z3MSkYFjfO9dFbgc/q0LovsdOEaPjnf1sj9z8i8XozBhn3/Exgx/AVwXGgclJeTxxFhOdGib+pVVpxsnSozuVU4zRD3puluVh5rnjv/P+rVDo3Y+Onv5xxnSJwJdmwyo0MksBgwDI77QuXxb/hb3dSw9U92VOEbyqZbNf/5MMCT/YltmVMvHZv9uxMeUkpzl7fu/M7Vecab3ycYr2hJzYG/2L9s5luJrljMPJjF8kw1nJedzV+EsYDTWlcrK/DxLzgJk0QC7OChbqRRQLagdNgIENOMWwhmv9ewsHledj0lFdzRtVh1tBCn75eqWyU2qierwVySU2/S3Jum47u9qYwOkeASSxrlDkd+uFW6/Zww9La/1Gp6O9wyl2TIwgtCCaQDoc8jkFqOzN/x8XdZMPazyqc+//kX22dgLEMoXIbvlXVcyGvIXXAvERUaKayI5le6+jQui+RqVhn3eUrWkUKCdRUWnDm5xGwqh1q2AB4LIUUss0Hxnq4mKZ9w2vGWh2gBQblUol5um5KQ7lUnPn7Uiwvgx5v/fZKcDr7iPdVCBGJWKmv0BsNiADgTJiuGsQyOMQy/YMu/0adKFl4Qi1RnH0jwUZLATGZ5rsMq0d5TOnv4ubgJ3TWF6SSIfwTweJQgumOB6rqt0I1/D8QSA6jUN0Gp7vvhxQlixzWGdxdvejVZ9YtFHoKRgdCjh0eutgPB0ECwfT0UAUMBBgz0y5FuMgTDoNXcyenuXjWz9npEkdqSkfF/gWn9jT6aZfnxEIUXpF07xcjghBdnfAKCoYUqR5e0V5bt9M/oEUgHJBJ77bKRnjgaohkIswzZFwEWc9X/kk9rBu6H12K+e6X9bN4WVkx7ElnfaPCXZ2hXaeiKmP2r8x986nsE/UKlero6DGzt5PJX4lFQsqMKLwiDk4xFqm9T2zsnctslnZfzr5SsGvRWU0i7pf2fXcIMeywnC0W81gXhvWIx+CicMEgkPr3TCT4ME3TKM8TVWUXiK9mLcDehoLIcQ6dJUsBIxg7RdNsg/QWKiWTIMvAmfX7DkSkLbh35Mfe5U11afWjRH9/vzySmbIK//uxXvycYH7/c4YN5219r0c/oieZikroswofjGR+VzBQ1aCpwm0iHeXEJ0LgACMZL5ziaLOvrPqGz7XGXPBTJnllK6gLRNCOa77CEk2VAEJyh8Y2nfvu03G3OYQFjT1X+8kBt4dIWbt/yb/cuqrh77gfc5y7VxJqDD+/8n8CCUrEuKF1Jt2nWQMaLPPH4At+g1bw55gbV4xd2beYG6iyG+wyK/AacoUmt7V4pt58mMsAuqQQWiLAnG8sCBJLu06zRMFbliVWWId8HKeip1S2tfWe1ynF/Qn8ai5HgEeSCS1gw8CdFxpMA/qR7dEgkY0WeSGXhJBn2K7dZvc1KnbVN5RQp3K2ZlT3cQD7uNR0FjKXokEhGqzzh/tREWv1mxVQznqNDIolGHkihQMF+4fe4FgRFh/QgHaMPJ8u4EgRFh9FJquUZs4Kg6DD2ScbL0TEhCIoOiEQxKv/CFIoOiGSR9oKg6IBIJWknCIoOiHQipYKg6IBId5ImCJIBMRpJmCCoq4QYC8RFEBQdEGOVmARB0QExXggrCIoOiPHMEEFQdEAgvoMXJJQUSAjEeGZMrMVCIBLF/wcAAP//ettpiq6bhWEAAAAASUVORK5CYII=",
- "key": "XfNTN1gQyvA2AcNOu1UN"
-}
-```
-
-#### Using a Web Page With Secondary Checks
-
-POST /api/v1.0/cdk/page/submit-check
-
-Header Content-Type:application/json
-
-Request parameters:
-
-
-| Request parameter | Type | Required | Description |
-| ------------------- | ------ | -------- | ------------------------------------------------- |
-| client_id | String | Yes | The Client ID of the game |
-| gift_code | String | Yes | The gift code entered by the user |
-| server_code | String | Yes | Server code |
-| character_id | String | Yes | User ID |
-| nonce_str | String | Yes | A random string used for this request; it’s recommended to use 5 digits |
-| verify_captcha_key | String | Yes | The key of the CAPTCHA |
-| verify_captcha_code | String | Yes | The CAPTCHA code entered by the user |
-| ext | String | No | This field will be passed to the game’s interface for secondary checks |
-
-#### Using a Web Page Without Secondary Checks
-
-POST /api/v1.0/cdk/page/submit-send
-
-Header Content-Type:application/json
-
-Request parameters:
-
-| Request parameter | Type | Required | Description |
-| ------------------- | ------ | -------- | ------------------------------ |
-| client_id | String | Yes | The Client ID of the game |
-| gift_code | String | Yes | The gift code entered by the user |
-| server_code | String | Yes | Server code |
-| character_id | String | Yes | User ID |
-| nonce_str | String | Yes | A random string used for this request; it’s recommended to use 5 digits |
-| verify_captcha_key | String | Yes | The key of the CAPTCHA |
-| verify_captcha_code | String | Yes | The CAPTCHA code entered by the user |
-
-## Miscellaneous
-
-### Error Codes
-
-| Error code | Description |
-| ------------ | ---------------------------------------------- |
-| 100001 | Invalid input |
-| 100002 | Invalid account |
-| 100003 | Other error |
-| 100004 | Wrong CAPTCHA |
-| 100005 | Parameter error |
-| 100006 | Quota exceeded |
-| 100007 | Batch quota exceeded |
-| 100008 | The event hasn’t started yet or has already ended |
-| 100009 | Not open yet |
-| 100010 | Out of scope |
-| 100011 | Too many requests |
-| 100012 | The user redeemed the same gift too many times |
-| 100013 | Invalid CDKEY |
-| 100014 | The server is responding slower than usual |
-| 100015 | Failed to deliver the items |
-| 100016 | Invalid gift code |
-| 100017 | Invalid event |
-| 100018 | Incorrect interface used for the current event |
-| 500001 | Server error |
-| 500002 | Failed to validate with an internal interface |
-
-### Comparison Operators
-
-| Comparison operator code | Description |
-| --------- | -------- |
-| lt | Less than |
-| le | Less than or equal to |
-| eq | Equal to |
-| ne | Not equal to |
-| ge | Greater than or equal to |
-| gt | Greater than |
-
-### IP Addresses of the System
-
-If your interface can only accept a limited number of IP addresses, please add the IP address listed below.
-
-| TapTap edition | IP |
-| --------------- | ------------- |
-| China (taptap.cn) | 59.110.228.98 |
-| Other countries and regions (taptap.io) | 8.214.95.148 |
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/no-server-guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/no-server-guide.mdx
deleted file mode 100644
index 9833e790c..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/tds-gift/no-server-guide.mdx
+++ /dev/null
@@ -1,62 +0,0 @@
----
-title: Serverless Exchange Development Guide
-sidebar_label: Serverless Exchange
-sidebar_position: 3
----
-
-:::tip
-This article is to explain the access process of serverless redemption method. If you need complete development content, please refer to [**Guide**](/sdk/tds-gift/guide/).
-:::
-
-## Present
-
-Docking gift redemption code requires the game party to maintain one or two web interfaces, which is relatively cumbersome. Therefore, we have simplified the redemption process as follows:
-
-** The game party requests the redemption interface for redemption, and judges whether the redemption is successful based on the information returned by the interface. **
-
-In this way, the game party only needs to call one interface to complete the redemption process.
-
-:::tip
-The exchange results received directly by the client can be intercepted, so the security of this method is not as high as other methods.
-:::
-
-## Matchmaking
-
-### Redemption interface
-
-The domain name is divided into two addresses according to the developer centre, and the gift pack data of the two addresses are not interoperable:
-
-| TapTap Version | Domain Name |
-| --------------- | ------------------------- |
-| China taptap.cn | https://poster-api.xd.cn |
-| Overseas taptap.io | https://poster-api.xd.com |
-
-The redemption interface parameters are described in detail in the [**Developer's Guide - Serverless Redemption**](/sdk/tds-gift/guide/#redeeming-without-a-server-1).
-
-A signature needs to be generated when initiating a redemption. The signature of the service-less redemption interface is calculated from the Client Id and other information, see [**Documentation**](/sdk/tds-gift/guide/#signature) for the signature calculation process.
-
-### Return value
-
-#### Result
-
-Judge whether the exchange behaviour is successful or not by whether the error field in the return value is 0. If the error field is judged not to be 0, you can see the specific reason according to [**error-codes**](/sdk/tds-gift/guide/#error-codes).
-
-#### Verification
-
-To prevent the return information from being tampered with, it is recommended that you take the following measures:
-1. To prevent replay attacks, you need to verify whether the return timestamp is too different from the client's time;
-2. Calculate a signature based on the return value and verify that it matches the signature in the return value. The signature calculation process is described in [**Documentation**](/sdk/tds-gift/guide/#signature);
-2. It is recommended that the name of the gift content can be an encrypted name.
-
-#### Prize content
-
-The prize content corresponding to the redemption code can be obtained based on the content or content_obj field in the return value. The difference is that content is a nested json string and parsing it directly as an object may result in an error.
-
-The prize content setting is not limited to English and Chinese. In order to facilitate development and judgment, it is recommended to maintain a prop list.
-
-## Other
-### Development Environment
-
-Using the serverless redemption interface to distinguish the formal testing environment requires creating different applications in the Developer Center.
-
-If it is not possible to deal with environmental issues by application, it is recommended to use the [without secondary check redemption interface](/sdk/tds-gift/guide/#without-secondary-check) to complete the redemption by configuring server information and configuring the server code to be forwarded to different environments .
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/update/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/sdk/update/_category_.json
deleted file mode 100644
index 3ca55cc08..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/update/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "唤起更新",
- "collapsed": true,
- "position": 12
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/sdk/update/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/sdk/update/guide.mdx
deleted file mode 100644
index c0016d47e..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/sdk/update/guide.mdx
+++ /dev/null
@@ -1,659 +0,0 @@
----
-title: Updates Integration Guide
-sidebar_label: Guide
----
-
-import MultiLang from "/src/docComponents/MultiLang";
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-import { Conditional } from "/src/docComponents/conditional";
-import AndroidFaq from "../_partials/android-package-visibility.mdx";
-
-When you release a new version of your game and wish the existing players to upgrade to the new version, you may want to display a notification in the game. With TapTap’s Updates function, a pop-up can be triggered when the SDK detects a new version released on the TapTap store. The player can tap a button within the pop-up to quickly jump to the Taptap app to update the game.
-
-## Integrating the SDK
-
-
-
-<>
-
-You can add the SDK **either manually or with the Unity Package Manager**.
-
-If you choose to use the Unity Package Manager, you should add the following dependencies into `Packages/manifest.json`:
-
-
- {`"dependencies":{
- "com.taptap.tds.common":"https://github.com/TapTap/TapCommon-Unity.git#${sdkVersions.taptap.unity}",
- "com.leancloud.storage": "https://github.com/leancloud/csharp-sdk-upm.git#storage-${sdkVersions.leancloud.csharp}",
-}`}
-
-
-If you choose to manually import the SDK, you should:
-
-* In the [download page](/tap-download), click `TapSDK Unity` to download `TapSDK-UnityPackage.zip`.
-* Go to your Unity project, navigate to **Assets > Import Package > Custom Package**, select the `TapTap_Common` module from unzipped SDK.
-* Download [LeanCloud-SDK-Storage-Unity.zip](https://github.com/leancloud/csharp-sdk/releases), unzip it as a `Plugins` folder, and drag and drop the folder into Unity.
-
->
-<>
-
-[Download](/tap-download) the TapSDK and add the `TapCommon` module to your game:
-
-
- {`repositories{
- flatDir {
- dirs 'libs'
- }
-}
-dependencies {
- ...
- implementation (name:'TapCommon_${sdkVersions.taptap.android}', ext:'aar')
-}`}
-
-
->
-<>
-
-[Download](/tap-download) the TapSDK and add the `TapCommon` module to your game:
-
-{`TapCommonSDK.framework`}
-
->
-
-
-
-## Open the TapTap App to Check for Updates
-
-:::tip
-Starting from TapSDK 3.3.0, the logic for updating the game has been optimized. You can have the game open a custom webpage if it fails to open the TapTap app. TapSDK 3.3.0 is still compatible with the APIs introduced in the earlier versions.
-With TapSDK 3.3.0, you won’t need to check if the TapTap app has been installed before you use the following APIs. We recommend you to use the new APIs if you are using TapSDK 3.3.0 and later.
-:::
-
-
-<>
-
-Open the TapTap app to check for updates. If failed, open the game’s page on the web app:
-
-
-
-```cs
-// For Mainland China
-bool isSuccess = await TapCommon.UpdateGameAndFailToWebInTapTap(string appId);
-
-// For other countries/regions
-bool isSuccess = await TapCommon.UpdateGameAndFailToWebInTapGlobal(string appId);
-```
-
-
-
-
-
-```cs
-bool isSuccess = await TapCommon.UpdateGameAndFailToWebInTapGlobal(string appId);
-```
-
-
-
-Open a custom webpage if failed to open the TapTap app:
-
-
-
-```cs
-// For Mainland China
-bool isSuccess = await TapCommon.UpdateGameAndFailToWebInTapTap(string appId, string webUrl);
-
-// For other countries/regions
-bool isSuccess = await TapCommon.UpdateGameAndFailToWebInTapGlobal(string appId, string webUrl);
-```
-
-
-
-
-
-```cs
-bool isSuccess = await TapCommon.UpdateGameAndFailToWebInTapGlobal(string appId, string webUrl);
-```
-
-
-
->
-<>
-
-Open the TapTap app to check for updates. If failed, open the game’s page on the web app:
-
-
-
-```java
-// For Mainland China
-TapGameUtil.updateGameAndFailToWebInTapTap(context, "your app id");
-
-// For other countries/regions
-TapGameUtil.updateGameAndFailToWebInTapGlobal(context, "your app id");
-```
-
-
-
-
-
-```java
-TapGameUtil.updateGameAndFailToWebInTapGlobal(context, "your app id");
-```
-
-
-
-Open a custom webpage if failed to open the TapTap app:
-
-
-
-```java
-// For Mainland China
-TapGameUtil.updateGameAndFailToWebInTapTap(context, "your app id", "your website url");
-
-// For other countries/regions
-TapGameUtil.updateGameAndFailToWebInTapGlobal(context, "your app id", "your website url");
-```
-
-
-
-
-
-```java
-TapGameUtil.updateGameAndFailToWebInTapGlobal(context, "your app id", "your website url");
-```
-
-
-
->
-<>
-
-```objc
-// Due to Apple’s restrictions, TapTap for iOS doesn’t offer the Updates function
-```
-
->
-
-
-
-
-
-If you are not using TapSDK 3.3.0 or later
-
-**This section is not applicable for TapSDK 3.3.0 and later**. If you are using TapSDK 3.3.0 and later, please use the APIs mentioned earlier on this page.
-
-**Check if TapTap is installed**:
-
-
-
-<>
-
-```cs
-// For Mainland China
-TapCommon.IsTapTapInstalled(installed =>
-{
- if (installed) {
- Debug.Log("TapTap is installed");
- }
-});
-
-// For other countries/regions
-TapCommon.IsTapTapGlobalInstalled(installed =>
-{
- if (installed) {
- Debug.Log("TapTap is installed");
- }
-});
-```
-
->
-<>
-
-Import `TapCommon.aar` and access the interfaces from `TapGameUtil`.
-
-```java
-import com.tds.common.utils.TapGameUtil;
-
-// For Mainland China
-if(TapGameUtil.isTapTapInstalled(this)){
- Log.d(TAG, "TapTap is installed");
-}
-// For other countries/regions
-if(TapGameUtil.isTapGlobalInstalled(this)){
- Log.d(TAG, "TapTap is installed");
-}
-```
-
->
-<>
-
-Import `TapCommon.framework` and add `tapsdk`, `taptap` and `tapiosdk` under `LSApplicationQueriesSchemes` in `info.plist`.
-
-```objc
-#import
-// For Mainland China
-BOOL isInstalled = [TapGameUtil isTapTapInstalled];
-// For other countries/regions
-BOOL isInstalled = [TapGameUtil isTapGlobalInstalled];
-```
-
->
-
-
-
-**Open TapTap to update the game**:
-
-
-
-```cs
-TapCommon.UpdateGameInTapTap("appid", callSuccess =>
-{
- if (callSuccess) {
- Debug.Log("TapTap opened");
- }
-});
-```
-
-```java
-if(TapGameUtil.updateGameInTapTap(this,"appid")){
- Log.d(TAG, "TapTap opened");
-}
-```
-
-```objc
-// Due to Apple’s restrictions, TapTap for iOS doesn’t offer the Updates function
-```
-
-
-
-
-
-
-
-## Open Game Reviews
-
-
-
-<>
-
-
-
-```cs
-// For Mainland China
-TapCommon.OpenReviewInTapTap(appId, openSuccess =>
-{
- if (openSuccess) {
- Debug.Log("Game reviews opened");
- }
-});
-
-// For other countries/regions
-TapCommon.OpenReviewInTapGlobal(appId, openSuccess =>
-{
- if (openSuccess) {
- Debug.Log("Game reviews opened");
- }
-});
-```
-
-
-
-
-
-```cs
-TapCommon.OpenReviewInTapGlobal(appId, openSuccess =>
-{
- if (openSuccess) {
- Debug.Log("Game reviews opened");
- }
-});
-```
-
-
-
->
-
-<>
-
-
-
-```java
-// For Mainland China
-if(TapGameUtil.openReviewInTapTap(this,"appid")){
- Log.d(TAG, "Game reviews opened");
-}
-
-// For other countries/regions
-if(TapGameUtil.openReviewInTapGlobal(this,"appid")){
- Log.d(TAG, "Game reviews opened");
-}
-```
-
-
-
-
-
-```java
-if(TapGameUtil.openReviewInTapGlobal(this,"appid")){
- Log.d(TAG, "Game reviews opened");
-}
-```
-
-
-
->
-
-<>
-
-```objc
-// Not supported yet
-```
-
->
-
-
-
-appid is a game’s unique identifier in the TapTap store.
-For example, in `https://www.taptap.cn/app/187168``https://www.taptap.io/app/187168`, `187168` is the `appid`.
-
-## FAQ
-
-### The TapTap app cannot be launched from the game on Android 11. Why?
-
-
-
-### If I’m not using the TapSDK, how can I open the TapTap app to update the game?
-
-Due to Apple’s restrictions, TapTap for iOS doesn’t support the Updates function. The following instructions are for Android only.
-
-If you are not using the TapSDK or using older versions of the TapSDK, you can follow the instructions below to manually open the TapTap app to update the game.
-
-Open the corresponding URL according to whether TapTap is installed on the device:
-
-- If TapTap is installed, open the TapTap app and jump to the game’s page.
-- If not, open the game’s page in a web browser. The player will follow the instructions on the page to install the TapTap app, open the app, and update the game within the app.
-
-URL for devices without TapTap:
-
-
-
-- For Mainland China: `https://l.taptap.cn/5d1NGyET?subc1=AppID`
-- For other countries/regions: `https://l.taptap.io/GNYwFaZr?subc1=AppID`
-
-
-
-
-- `https://l.taptap.io/GNYwFaZr?subc1=AppID`
-
-
-
-URL for devices with TapTap:
-
-
-
-- For Mainland China: `taptap://taptap.cn/app?app_id=AppID&source=outer|update`
-- For other countries/regions: `tapglobal://taptap.tw/app?app_id=AppID&source=outer|update`
-
-
-
-
-- `tapglobal://taptap.tw/app?app_id=AppID&source=outer|update`
-
-
-
-Make sure to replace the `AppID` in the URL. `AppID` is a game’s unique identifier in the TapTap store. For example, in `https://www.taptap.cn/app/187168``https://www.taptap.io/app/187168`, `187168` is the AppID.
-
-Keep in mind that you have to write the logic not only to open the URL but also to check whether TapTap is installed, as well as handle errors.
-
-Take a look at the code example below.
-
-
-Code example
-
-
-
-For Mainland China:
-
-```java
-package com.tds.common.utils;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-import java.util.Locale;
-
-public class TapGameUtil {
-
- private static final String TAG = TapGameUtil.class.getName();
-
- public static final String PACKAGE_NAME_TAPTAP = "com.taptap";
-
- public static final String CLIENT_URI_TAPTAP = "taptap://taptap.cn";
-
- public static final String DEFAULT_CLIENT_DOWNLOAD_URL_TAPTAP = "https://l.taptap.cn/5d1NGyET";
-
- public static boolean updateGameAndFailToWebInTapTap(Activity activity, String appId) {
- return updateGameInTapTap(activity, appId) || openWebDownloadUrlOfTapTap(activity, appId);
- }
-
- public static boolean updateGameAndFailToWebInTapTap(Activity activity, String appId, String webUrl) {
- if (TextUtils.isEmpty(webUrl)) {
- return updateGameAndFailToWebInTapTap(activity, appId);
- }
- return updateGameInTapTap(activity, appId) || openWebDownloadUrl(activity, webUrl);
- }
-
- public static boolean isTapTapInstalled(Context context) {
- return isTapClientInstalled(context, PACKAGE_NAME_TAPTAP);
- }
-
- public static boolean isTapClientInstalled(Context context, String clientPackageName) {
- if (context != null && !TextUtils.isEmpty(clientPackageName)) {
- boolean TapTapInstalled = false;
- try {
- PackageInfo packageInfo = context.getPackageManager().getPackageInfo(clientPackageName, 0);
- if (null != packageInfo) {
- TapTapInstalled = true;
- }
- } catch (Exception e) {
- Log.e(TAG, clientPackageName + " isInstalled=false");
- }
- return TapTapInstalled;
- }
- return false;
- }
-
- public static boolean updateGameInTapTap(Activity activity, String appId) {
- return updateGameInTapClient(activity, appId, CLIENT_URI_TAPTAP);
- }
-
- public static boolean updateGameInTapClient(Activity activity, String appId, String clientUri) {
- if (activity != null && !TextUtils.isEmpty(appId) && !TextUtils.isEmpty(clientUri)) {
- try {
- Uri uri = Uri.parse(String.format(Locale.US,
- "%s/app?app_id=%s&source=outer|update", clientUri, appId));
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- activity.startActivity(intent);
- } catch (Exception e) {
- Log.e(TAG, clientUri + "updateGameInTapTap failed");
- return false;
- }
- return true;
- }
- Log.e(TAG, clientUri + "updateGameInTapTap failed");
- return false;
- }
-
- public static boolean openReviewInTapTap(Activity activity, String appId) {
- return openReviewInTapClient(activity, appId, CLIENT_URI_TAPTAP);
- }
-
- public static boolean openReviewInTapClient(Activity activity, String appId, String clientUri) {
- if (activity != null && !TextUtils.isEmpty(appId) && !TextUtils.isEmpty(clientUri)) {
- try {
- Uri uri = Uri.parse(String.format(Locale.US,
- "%s/app?tab_name=review&app_id=%s", clientUri, appId));
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- activity.startActivity(intent);
- } catch (Exception e) {
- Log.e(TAG, clientUri + "openTapTapReview failed");
- return false;
- }
- return true;
- }
- Log.e(TAG, clientUri + "openTapTapReview failed");
- return false;
- }
-
- public static boolean openWebDownloadUrlOfTapTap(Activity activity, String appId) {
- return openWebDownloadUrl(activity, String.format(Locale.US, DEFAULT_CLIENT_DOWNLOAD_URL_TAPTAP + "?subc1=%s", appId));
- }
-
- public static boolean openWebDownloadUrl(Activity activity, String url) {
- if (activity != null && !TextUtils.isEmpty(url)) {
- try {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setData(Uri.parse(url));
- activity.startActivity(intent);
- } catch (Exception e) {
- Log.e(TAG, "openWebUrl fail");
- return false;
- }
- return true;
- }
- return false;
- }
-
-}
-```
-
-For other countries/regions:
-
-
-
-```java
-package com.tds.common.utils;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-import java.util.Locale;
-
-public class TapGameUtil {
-
- private static final String TAG = TapGameUtil.class.getName();
-
- public static final String PACKAGE_NAME_TAPTAP_GLOBAL = "com.taptap.global";
-
- public static final String CLIENT_URI_TAPTAP_GLOBAL = "tapglobal://taptap.tw";
-
- public static final String DEFAULT_CLIENT_DOWNLOAD_URL_TAPTAP_GLOBAL = "https://l.taptap.io/GNYwFaZr";
-
- public static boolean updateGameAndFailToWebInTapGlobal(Activity activity, String appId) {
- return updateGameInTapGlobal(activity, appId) || openWebDownloadUrlOfTapGlobal(activity, appId);
- }
-
- public static boolean updateGameAndFailToWebInTapGlobal(Activity activity, String appId, String webUrl) {
- if (TextUtils.isEmpty(webUrl)) {
- return updateGameAndFailToWebInTapGlobal(activity, appId);
- }
- return updateGameInTapGlobal(activity, appId) || openWebDownloadUrl(activity, webUrl);
- }
-
- public static boolean isTapGlobalInstalled(Context context) {
- return isTapClientInstalled(context, PACKAGE_NAME_TAPTAP_GLOBAL);
- }
-
- public static boolean isTapClientInstalled(Context context, String clientPackageName) {
- if (context != null && !TextUtils.isEmpty(clientPackageName)) {
- boolean TapTapInstalled = false;
- try {
- PackageInfo packageInfo = context.getPackageManager().getPackageInfo(clientPackageName, 0);
- if (null != packageInfo) {
- TapTapInstalled = true;
- }
- } catch (Exception e) {
- Log.e(TAG, clientPackageName + " isInstalled=false");
- }
- return TapTapInstalled;
- }
- return false;
- }
-
- public static boolean updateGameInTapGlobal(Activity activity, String appId) {
- return updateGameInTapClient(activity, appId, CLIENT_URI_TAPTAP_GLOBAL);
- }
-
- public static boolean updateGameInTapClient(Activity activity, String appId, String clientUri) {
- if (activity != null && !TextUtils.isEmpty(appId) && !TextUtils.isEmpty(clientUri)) {
- try {
- Uri uri = Uri.parse(String.format(Locale.US,
- "%s/app?app_id=%s&source=outer|update", clientUri, appId));
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- activity.startActivity(intent);
- } catch (Exception e) {
- Log.e(TAG, clientUri + "updateGameInTapTap failed");
- return false;
- }
- return true;
- }
- Log.e(TAG, clientUri + "updateGameInTapTap failed");
- return false;
- }
-
- public static boolean openReviewInTapGlobal(Activity activity, String appId) {
- return openReviewInTapClient(activity, appId, CLIENT_URI_TAPTAP_GLOBAL);
- }
-
- public static boolean openReviewInTapClient(Activity activity, String appId, String clientUri) {
- if (activity != null && !TextUtils.isEmpty(appId) && !TextUtils.isEmpty(clientUri)) {
- try {
- Uri uri = Uri.parse(String.format(Locale.US,
- "%s/app?tab_name=review&app_id=%s", clientUri, appId));
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- activity.startActivity(intent);
- } catch (Exception e) {
- Log.e(TAG, clientUri + "openTapTapReview failed");
- return false;
- }
- return true;
- }
- Log.e(TAG, clientUri + "openTapTapReview failed");
- return false;
- }
-
- public static boolean openWebDownloadUrlOfTapGlobal(Activity activity, String appId) {
- return openWebDownloadUrl(activity, String.format(Locale.US, DEFAULT_CLIENT_DOWNLOAD_URL_TAPTAP_GLOBAL + "?subc1=%s", appId));
- }
-
- public static boolean openWebDownloadUrl(Activity activity, String url) {
- if (activity != null && !TextUtils.isEmpty(url)) {
- try {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setData(Uri.parse(url));
- activity.startActivity(intent);
- } catch (Exception e) {
- Log.e(TAG, "openWebUrl fail");
- return false;
- }
- return true;
- }
- return false;
- }
-
-}
-```
-
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/billboard/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/shadow/billboard/_category_.json
deleted file mode 100644
index 657b5a2e6..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/billboard/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "公告系统",
- "collapsed": true,
- "position": 8
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/billboard/design-billboard.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/billboard/design-billboard.mdx
deleted file mode 100644
index e3653707b..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/billboard/design-billboard.mdx
+++ /dev/null
@@ -1,206 +0,0 @@
----
-title: Billboard Design Guideline
-sidebar_position: 3
-slug: /sdk/billboard/design-billboard/
----
-
-
-
-
-
-import { Background, Figure } from "/src/docComponents/doc";
-import useBaseUrl from "@docusaurus/useBaseUrl";
-
-## Types of Billboard
-
-
-
-
-
-## A.Navigation Template Type
-
-### Landscape screen cut size
-
-Custom landscape billboard need to upload top navigation bar background, left tab bar card background and pop-up window background image cutout, cutout are triple image @3X size, file type: JPG. PNG.
-
-
-
-
-
-### Landscape screen customization suggestions
-The characters must be clearly visible. Therefore, it is recommended to use dark description characters when the background image is light and light description characters when the background image is dark.
-
-
-
- The text color contrasts clearly with the background color, making it easy to read, while the use of accent colors and secondary colors makes the overall structure clear.
- >
- }
- imgSrc={useBaseUrl("/img/design-billboard/2.2.png")}
- imgAlt=""
- />
-
- Text color and background color contrast blurred, inconvenient to read, primary and secondary colors are relatively similar, the overall structure of the hierarchy is not clear.
- >
- }
- imgSrc={useBaseUrl("/img/design-billboard/2.3.png")}
- imgAlt=""
- />
-
-
-### Portrait screen cut size
-Custom portrait billboard need to upload the top navigation bar background, tab bar card background, pop-up window background image and back to previous level button cutout, cutout are triple image @3X size, file type: JPG. PNG.
-
-
-
-
-
-### Portrait screen customization suggestions
-The characters must be clearly visible. Therefore, it is recommended to use dark description characters when the background image is light and light description characters when the background image is dark.
-
-
-
- The text color contrasts clearly with the background color, making it easy to read, while the use of accent colors and secondary colors makes the overall structure clear.
- >
- }
- imgSrc={useBaseUrl("/img/design-billboard/2.5.png")}
- imgAlt=""
- />
-
- Text color and background color contrast blurred, inconvenient to read, primary and secondary colors are relatively similar, the overall structure of the hierarchy is not clear.
- >
- }
- imgSrc={useBaseUrl("/img/design-billboard/2.6.png")}
- imgAlt=""
- />
-
-
-The text icons on the Back to previous level button are all part of the button cutout, and the cutout requires attention to.
-
-
-
- The button cutout must contain text or icon content.
- >
- }
- imgSrc={useBaseUrl("/img/design-billboard/2.7.png")}
- imgAlt=""
- />
-
- Only the background cutout of the button is available, and the button meaning is not clear.
- >
- }
- imgSrc={useBaseUrl("/img/design-billboard/2.8.png")}
- imgAlt=""
- />
-
-
-### Global text type and font suggestions
-Global text types can be divided into 4 types according to color configuration, which are default text, auxiliary text, announcement highlight text and announcement link text, fonts can be uploaded and configured by yourself, copyright issues are borne by the vendor.
-
-
-
-
-
-
-
- The text color of the main text is clear and easy to read, the color contrast between the auxiliary text and the highlighted text of the announcement is clear, and the text color of the announcement link is used correctly.
- >
- }
- imgSrc={useBaseUrl("/img/design-billboard/2.10.png")}
- imgAlt=""
- />
-
- The text color of the main text is placed on the wrong background color, which is not easily recognizable, the color contrast between the auxiliary text and the highlighted text of the announcement is not strong enough, and the text of the announcement link has no clickable jumping sense.
- >
- }
- imgSrc={useBaseUrl("/img/design-billboard/2.11.png")}
- imgAlt=""
- />
-
-
-### More configurable areas globally with cutout sizes
-In addition to fonts and text colors, there are more customizable details across the board such as close buttons, small red dots, empty status illustrations and announcement status labels, all cutouts are triple @3X size, file type: JPG. PNG.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-## B.Image Template Type
-
-Please look forward to the relevant configuration documents~
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/billboard/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/billboard/features.mdx
deleted file mode 100644
index 1fd969159..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/billboard/features.mdx
+++ /dev/null
@@ -1,202 +0,0 @@
----
-title: Billboard Introduction
-sidebar_label: Introduction
-sidebar_position: 1
-slug: /sdk/billboard/features/
----
-
-
-
-
-
-
-import { Conditional } from "/src/docComponents/conditional";
-
-## Overview
-
-Billboard allows developers to publish and edit billboard contents on the Developer Center, which players can see when they open the game.
-
-## Highlights
-
-- Reduced costs: We’ll give you everything you need to create a billboard in your game so you don’t have to build it yourself.
-- Easy-to-use tools: With the tools we provide, you can easily publish contents to the billboard in your game.
-- Customizable configurations: You can upload custom themes and configure properties for content distribution without writing a single line of code.
-- Outstanding compatibility: We offer user interfaces built with WebView, which supports layouts containing images and texts better compared to those built with game engines.
-- Convenience to maintain: The Billboard service doesn’t depend on other services and you don’t have to update the SDK in order to update the contents of the billboard in your game.
-
-## Getting Started
-
-- For an account to have access to the Billboard service, please go to *Manage Permissions* and give this account the *Game Administrator* permission.
-- To make the Billboard services accessible to the players, a custom domain has to be added to the game.
-- Follow **[Billboard Design Guideline](../design-billboard/)** to design the assets needed for the billboard in the game.
-- The minimum TapSDK version required to use Billboard is 3.12.0 for iOS and Android and 3.12.1 for Unity.
-
-## Feature Introduction
-
-### Basic Flow
-
-![](https://capacity-files.lcfile.com/qVCnc1ikCQTTYw8FC5AiLQWpAm6UnHUB/liuchengtu.png)
-
-### Enable the Service
-
-To start using Billboard in your game, go to **Developer Center > Game Services > Configuration** and *Turn On* the Billboard service.
-
-![](https://capacity-files.lcfile.com/uo1BNIYGnlMgcfB3IJGSN88MtEHxBTm5/io-open_billboard.png)
-
-
-
-### Configure Your Domain
-
-To ensure that users can have stable access to the Billboard service, we recommend that you add your own domain to your game. See **[Domain](/sdk/domain/guide/)** for more information on how to add your domain.
-
-![](https://capacity-files.lcfile.com/Az9ieoDfeykXfNUIjWcUyxoTFxYuRBmk/bond_url.png)
-
-
-
-### Configure Templates
-
-You can decide which template to show when players open the billboard in your game.
-
-- **Navigation Bar**: Designed for games with more announcements or publish announcements relatively more frequently.
-
-- **Poster**: Designed for games with fewer announcements or want a banner on the billboard. **(coming soon)**
-
-![](https://capacity-files.lcfile.com/E6MqYqVHaqh4jl4w2wnMT7cw82qWLASA/io-select_mod.png)
-
-If you choose to use the Navigation Bar for your game, you will be able to customize the UI of the billboard to make it better fit the visual style of your game.
-
-- We offer a default style for the billboard. You can reset the style of the billboard to this default style at any time.
-- Configurations available to all games: Default Text Color, Secondary Text Color, Highlighted Text, Link Text, Global Font, Red dot, Close Button, Empty, and Icons for different types of announcement
-- Configurations required for landscape games: Top Bar Background, Content Background Image, Default Background, and Background when selected
-- Configurations required for portrait games: Top Bar Background, Content Background Image, Default Background, and Go-back Button
-
-:::note
-
-1. Please follow **[Billboard Design Guideline](../design-billboard/)** when designing the look of the billboard in your game.
-
-2. To ensure that the billboard can be opened without delay, each image uploaded should be within **100 KB**.
-
-3. To ensure that the billboard can be opened without delay, the font uploaded should be within **5 MB**.
-
-:::
-
-![](https://capacity-files.lcfile.com/IQJqjmWkKzg4ASs7Lneax4aF377J5l1L/io-edit_mod.png)
-
-**Regarding Icons for different types of announcement**
-
-You can use different icons for different types of announcements to get these announcements better distinguished. We offer 6 icons that you can use directly for different types, though it is not a requirement for you to pick an icon for each type.
-
-![](https://capacity-files.lcfile.com/v3gLbaELXfBbqrVpInrvib3OLy1RjKRG/billboard_label.png)
-
-
-### Configure Properties
-
-Setting up properties helps you better **deliver your announcements**. You can customize the properties depending on how you are publishing your game.
-
-- There are two default properties provided: *Platform* and *Region*. These two properties cannot be deleted.
- - *Platform* has 2 default fields: *iOS* and *Andriod-TapTap*.
- - *Region* has 9 default fields: *China mainland*, *Macao China*, *Hong Kong China*, *Taiwan China*, *Japan*, *United States of America*, *South Korea*, *Indonesia*, and *Thailand*.
-- You can add at most 10 custom properties. You will be able to edit and delete the custom properties after you create them. Different properties cannot have the same parameters or descriptions.
- - You can add custom fields under each property.
- - Different fields cannot have the same parameters or descriptions.
-- Here are some recommended properties you can add: *Version Code*, *Server*, *Environment*, *Package*, and *Platform*.
-
-:::caution
-
-1. The parameter of a field can contain numbers, letters, and underscores. It is case-sensitive and can be no longer than 20 characters.
-
-2. The parameters you entered here will be used by clients and our server when they communicate. Different fields cannot have the same parameters.
-
-3. Once a property or field is created, you cannot update its parameter anymore.
-
-:::
-
-![](https://capacity-files.lcfile.com/OimaLKFzKOvQdIvvqeMd7Jk2ksoah12A/io-weidu_config.png)
-
-### Create Announcements
-
-When you create an announcement, you need to provide its content as well as settings. You will be able to **release the announcement immediately** or **save it as a draft**. Released announcements cannot be converted to drafts anymore, but you will be able to **hide** or **release (unhide)** a released announcement.
-
-For each announcement you create, you need to provide a Long Title, a Short Title, and its Content. You can set up multiple languages for the announcement.
-
-![](https://capacity-files.lcfile.com/CVsJKeJbayYO21JGUIUE8u76LwzwSyf4/io-release_billboard_content.png)
-
-You will be configuring *Display as*, *Type*, *Landing on*, *Distribution*, and *Date Settings* when you create an announcement.
-
-- Display as: You can associate the content of the announcement to a template. For now, you can only pick *Navigation Bar*.
-- Type: You can choose from *Update*, *Maintenance*, *New Arrival*, *Event*, *Gameplay*, *Test*, and *Sever Closing*.
-- Landing on: You can choose from *Content of Announcement*, *Redirection*, and *In-Game Scene*.
-- Distribution: You can choose either to release the announcement to **everyone** or to **specify a number of distributions**. You can pick multiple options for each distribution.
-- Date Settings: You can set a schedule for the announcement **to be released** and **to be hidden**.
-
-:::tip
-
-1. You can specify a callback URL for jumping from the announcement back to the game.
-
-2. You can still edit the distributions of an announcement after you release it.
-
-3. A hidden announcement will be released again when you edit and save it.
-
-:::
-
-![](https://capacity-files.lcfile.com/JjQMdA5TnRHY5uq6pHJvKmphwF8aszBq/io-release_billboard_config.png)
-
-### Sort Announcements
-
-You can give a number to each announcement to **control the order** of all the announcements of your game. Announcements with larger numbers will be displayed at the beginning of the list.
-
-You will be able to **filter announcements** according to **No.**, **Status**, **Display as**, **Region**, **Platform**, **Long Title**, and **Release Date**.
-
-![](https://capacity-files.lcfile.com/gpeJ1qWNlxh4ex3JBtcjE8riSRs65bCY/io-billboard_list.png)
-
-### Duplicate Announcements
-
-You will be able to **duplicate** an announcement to the current game or a different game and use it as a draft. The properties of an announcement cannot be duplicated. An announcement duplicated from another one will have unlimited properties by default.
-
-![](https://capacity-files.lcfile.com/3q9r9JbddBqk21I1cmKikaSs9Dgd4QHS/io-copy_billboard.png)
-
-### Display of Announcements
-
-#### Navigation Bar
-
-When there is at least one unread message under the *Announcements* tab or the *Events* tab, a badge will be displayed. The badge will disappear when all the messages are read.
-
-When using Navigation Bar, the most recent 20 announcements will be displayed.
-
-The content of an announcement can contain highlighted text, images, videos, and links. In landscape mode, the content of the first announcement will be automatically displayed. In portrait mode, only the announcement list will be displayed. The user will be able to go back to the list after they open an announcement.
-
-A link can either be opened in an external browser or take the user to a module within the game. It may also take the player to the content of an announcement.
-
-![](https://img.tapimg.com/market/images/a002825ac59f0c0456ff180afe9d6899.png)
-
-![](https://img.tapimg.com/market/images/ff9b021f99609e6d45445e92ab59e6bb.png)
-
-#### Poster
-
-Coming soon.
-
-## FAQ
-
-**Q: Can I use multiple templates in the same game?**
-
-A: Yes. You can use Navigation Bar and Poster altogether in your game.
-
-**Q: Can a link be opened within the game?**
-
-A: For now, a link can only be opened in an external browser.
-
-**Q: If I choose *iOS* as the *Platform*, *China mainland* as the *Region*, and *S1* and *S2* as the *Server*, then who will be able to see the announcement?**
-
-A: Only users who use iOS, are in China mainland, and are on S1 or S2 will see the announcement. Android users and those who are on other servers won’t see the announcement.
-
-**Q: Are there only two types of templates at this time?**
-
-A: At this time, there are only two types of templates available. If you need more customizations on the UI of the dashboard, we recommend that you integrate the Billboard service into your game using our API.
-
-**Q: How do I specify the value for players to jump to a module in the game from a link in an announcement?**
-
-A: You can provide anything like a string, ID, or URL. Just make sure the value is a unique identifier.
-
-**Q: Can I customize the type of an announcement?**
-
-A: Unfortunately, you cannot customize the type of an announcement at this time.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/billboard/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/billboard/guide.mdx
deleted file mode 100644
index 37eb84993..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/billboard/guide.mdx
+++ /dev/null
@@ -1,778 +0,0 @@
----
-title: Billboard Guide
-sidebar_label: Guide
-sidebar_position: 2
-slug: /sdk/billboard/guide/
----
-
-
-
-
-
-
-import MultiLang from '/src/docComponents/MultiLang';
-import CodeBlock from '@theme/CodeBlock';
-import sdkVersions from '/src/docComponents/sdkVersions';
-import Languages from '../../sdk/_partials/languages.mdx';
-import { Conditional } from "/src/docComponents/conditional";
-
-:::tip
-Before integrating Billboard’s SDK into your game, please first **enable the service and configure your domains** according to [Feature Introduction](../features/#feature-introduction).
-:::
-
-## SDK Setup
-
-Please download the TapSDK from the [Download page](/tap-download) and follow the instructions below to import the Billboard module to your project.
-
-
-
-<>
-
-If you are building your game with Unity, you can import a `.unitypackage` file to your game, which includes the iOS module and the Android module. If you are building with other engines or for other platforms, you can import the iOS or Android module natively. Please refer to the documentation for iOS or Android for more information.
-
-**Unity requirement**: You will need Unity 2019.4 or higher to use the SDK.
-
-Supported OS versions:
-
-- Android: 5.0 and higher
-- iOS: 10.0 and higher, Xcode 14.1 or later
-
-
-{`"dependencies":{
- "com.taptap.tds.billboard": "https://github.com/TapTap/TapBillboard-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.login":"https://github.com/TapTap/TapLogin-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.common": "https://github.com/TapTap/TapCommon-Unity.git#${sdkVersions.taptap.unity}",
- "com.taptap.tds.bootstrap": "https://github.com/TapTap/TapBootstrap-Unity.git#${sdkVersions.taptap.unity}",
- "com.leancloud.realtime": "https://github.com/leancloud/csharp-sdk-upm.git#realtime-${sdkVersions.leancloud.csharp}",
- "com.leancloud.storage": "https://github.com/leancloud/csharp-sdk-upm.git#storage-${sdkVersions.leancloud.csharp}",
-}`}
-
-
->
-
-<>
-
-
-{`repositories{
- flatDir {
- dirs 'libs'
- }
-}
-dependencies {
-
- implementation (name:'TapBillboard_${sdkVersions.taptap.android}', ext:'aar') // The Billboard service
- implementation (name:'TapCommon_${sdkVersions.taptap.android}', ext:'aar')
- implementation (name:'TapBootstrap_${sdkVersions.taptap.android}', ext:'aar')
-}`}
-
-
->
-
-<>
-
-
-{`// The Billboard service
-TapBillboardResource.bundle
-TapCommonResource.bundle
-TapBillboardSDK.framework
-TapCommonSDK.framework
-TapBootstrapSDK.framework
-`}
-
-
->
-
-
-
-
-## Initializing the SDK
-
-:::info
-You can initialize the SDK using one of the following two methods.
-:::
-
-### Initializing With the TapSDK
-
-If you have already initialized the TapSDK according to [Quickstart](/sdk/start/quickstart/#initialization), you will only need to import the Billboard module here and add the highlighted portions of the code below to your project.
-
-
-
-<>
-
-```cs
-using TapTap.Common;
-using TapTap.Bootstrap;
-// highlight-next-line
-using TapTap.Billboard;
-
-// highlight-start
-var dimensionSet = new HashSet>();
-KeyValuePair platformPair = new KeyValuePair("platform", "TapTap");
-KeyValuePair locationPair = new KeyValuePair("location", "CN");
-dimensionSet.Add(platformPair);
-dimensionSet.Add(locationPair);
-var templateType = "navigate"; // Optional
-var billboardServerUrl = "https://your-billboard-server-url"; // Developer Center > Your game > Game Services > Configuration > Domain > Billboard
-// highlight-end
-
-var config = new TapConfig.Builder()
- .ClientID("your_client_id") // Required; the Client ID obtained from the Developer Center
- .ClientToken("your_client_token") // Required; the Client Token obtained from the Developer Center
- .ServerURL("https://your_server_url") // Required; can be found on Developer Center > Your game > Game Services > Basic Information > Domain > Cloud Services API
- .RegionType(RegionType.CN) // Optional; CN means China mainland and IO means other countries and regions
- // highlight-next-line
- .TapBillboardConfig(dimensionSet, templateType, billboardServerUrl)
- .ConfigBuilder();
-TapBootstrap.Init(config);
-```
-
->
-
-<>
-
-**Attention: The Android SDK has to be initialized in the main thread.**
-
-
-
-To initialize for China mainland:
-
-```java
-// highlight-start
-Set> dimensionSet = new HashSet<>();
-dimensionSet.addAll(Arrays.asList(Pair.create("location", "CN"), Pair.create("platform", "TapTap")));
-String billboardServerUrl = "https://your-billboard-server-url"; // Developer Center > Your game > Game Services > Configuration > Domain > Billboard
-
-TapBillboardConfig billboardCnConfig = new TapBillboardConfig.Builder()
- .withDimensionSet(dimensionSet) // Optional
- .withServerUrl(billboardServerUrl) // Required; the custom domain for Billboard
- .withTemplate("navigate") // Optional; defaults to `navigate`
- .build();
-// highlight-end
-
-TapConfig tapConfig = new TapConfig.Builder()
- .withAppContext(gameMainActivity) // Required; provide the main `Activity` of the game
- .withClientId("your_client_id") // Required; the Client ID obtained from the Developer Center
- .withClientToken("your_client_token") // Required; the Client Token obtained from the Developer Center
- .withServerUrl("https://your_server_url") // Required; can be found on Developer Center > Your game > Game Services > Basic Information > Domain > Cloud Services API
- // highlight-next-line
- .withBillboardConfig(billboardCnConfig) // Required
- .withRegionType(TapRegionType.CN)
- .build();
-TapBootstrap.init(gameMainActivity, tapConfig);
-```
-
-To initialize for other countries and regions:
-
-
-
-```java
-// highlight-start
-Set> dimensionSet = new HashSet<>()
-dimensionSet.addAll(Arrays.asList(Pair.create("location", "XX"), Pair.create("platform", "TapTap")));
-
-TapBillboardConfig billboardIntlConfig = new TapBillboardConfig.Builder()
- .withDimensionSet(dimensionSet) // Optional
- .withTemplate("navigate") // Optional; defaults to `navigate`
- .build();
-// highlight-end
-
-TapConfig tapConfig = new TapConfig.Builder()
- .withAppContext(gameMainActivity) // Required; provide the main `Activity` of the game
- .withClientId("your_client_id") // Required; the Client ID obtained from the Developer Center
- .withClientToken("your_client_token") // Required; the Client Token obtained from the Developer Center
- .withServerUrl("https://your_server_url") // Required; can be found on Developer Center > Your game > Game Services > Basic Information > Domain > Cloud Services API
- // highlight-next-line
- .withBillboardConfig(billboardIntlConfig) // Required
- .withRegionType(TapRegionType.IO)
- .build();
-TapBootstrap.init(gameMainActivity, tapConfig);
-```
-
->
-
-<>
-
-
-
-To initialize for China mainland:
-
-```objectivec
-// highlight-start
-NSMutableSet *dimensionSet = [[NSMutableSet alloc] init];
-[dimensionSet addObject:[NSArray arrayWithObjects:@"platform", @"ios", nil]];
-[dimensionSet addObject:[NSArray arrayWithObjects:@"location", @"CN", nil]];
-
-TapBillboardConfig *billboardCnConfig = [TapBillboardConfig new];
-billboardCnConfig.templateType = @"navigate"; // Optional
-billboardCnConfig.diemensionSet = dimensionSet; // Optional
-billboardCnConfig.serverUrl = @"https://your-billboard-server-url"; // Developer Center > Your game > Game Services > Configuration > Domain > Billboard
-// highlight-end
-
-TapConfig *config = [TapConfig new];
-config.clientId = @"your_client_id"; // Required; the Client ID obtained from the Developer Center
-config.clientToken = @"your_client_token"; // Required; the Client Token obtained from the Developer Center
-config.region = TapSDKRegionTypeCN; // `TapSDKRegionTypeCN` means China mainland; `TapSDKRegionTypeIO` means other countries and regions
-config.serverURL = "https://your_server_url"; // Required; can be found on Developer Center > Your game > Game Services > Basic Information > Domain > Cloud Services API
-// highlight-next-line
-config.tapBillboardConfig = billboardCnConfig;
-[TapBootstrap initWithConfig:config];
-```
-
-To initialize for other countries and regions:
-
-
-
-```objectivec
-// highlight-start
-NSMutableSet *dimensionSet = [[NSMutableSet alloc] init];
-[dimensionSet addObject:[NSArray arrayWithObjects:@"platform", @"ios", nil]];
-[dimensionSet addObject:[NSArray arrayWithObjects:@"location", @"XX", nil]];
-
-TapBillboardConfig *billboardIntlConfig = [TapBillboardConfig new];
-billboardIntlConfig.templateType = @"navigate"; // Optional
-billboardIntlConfig.diemensionSet = dimensionSet; // Optional
-// highlight-end
-
-TapConfig *config = [TapConfig new];
-config.clientId = @"your_client_id"; // Required; the Client ID obtained from the Developer Center
-config.clientToken = @"your_client_token"; // Required; the Client Token obtained from the Developer Center
-config.region = TapSDKRegionTypeIO; // `TapSDKRegionTypeCN` means China mainland; `TapSDKRegionTypeIO` means other countries and regions
-config.serverURL = "https://your_server_url"; // Required; can be found on Developer Center > Your game > Game Services > Basic Information > Domain > Cloud Services API
-// highlight-next-line
-config.tapBillboardConfig = billboardIntlConfig;
-[TapBootstrap initWithConfig:config];
-```
-
->
-
-
-
-### Initializing the Billboard Service Independently
-
-If you prefer not to initialize the TapSDK with the `TapBootstrap` method mentioned above and would like to only initialize the Billboard service, you can:
-
-- **Copy the above code for initializing the TapSDK**.
-- **Change the last line** to:
-
-
-
-```cs
-TapBillboard.Init(config);
-```
-
-```java
-TapBillboard.init(tapConfig);
-```
-
-```objectivec
-[TapBillboard initWithConfig:config];
-```
-
-
-
-### Parameters
-
-
-
-* You will be providing both the API domain and the Billboard domain when initializing the SDK. See [Domain](/sdk/domain/guide/) for more information. Please make sure you have added your domain on **Developer Center > Your game > Game Services > Configuration > Domain**.
-
-
-
-* See [Configure Properties](../features/#configure-properties) for more information about `dimensionSet`. Here we are using the two default properties: Platform and Region.
-
-* `client_id`, `client_token`, and `RegionType` can be found on **Developer Center > Your game > Game Services > Configuration**.
-
-## Opening the Billboard
-
-
-
-```cs
-TapBillboard.OpenPanel((any, error) =>
-{
- if (error != null)
- {
- // Failed to open the billboard; see `error.code` and `error.errorDescription` for the reason
- } else
- {
- // Billboard is now open
- }
-}, () => {
- // Billboard is now closed
-});
-```
-
-```java
-TapBillboard.openPanel(BillboardActivity.this, new Callback() {
- @Override
- public void onError(TapBillboardException tapBillboardException) {
- // Failed to open the billboard; see `tapBillboardException.code` and `tapBillboardException.message` for the reason
- }
-
- @Override
- public void onSuccess(Void result) {
- // Billboard is now open
- }
-},new UserInteraction() {
- @Override
- public void onClose() {
- // Billboard is now closed; please switch to the main thread if you need to update the UI
- }
-});
-```
-
-```objectivec
-[TapBillboard openPanel:^(bool _, NSError *_Nullable error) {
- if (error) {
- // Failed to open the billboard; see `error.code` and `error.errorDescription` for the reason
- } else {
- // Billboard is now open
- }
-} closeCallback:^(void){
- // Billboard is now closed
-}];
-```
-
-
-
-## Obtaining Badge Statuses
-
-Badges can be used to tell whether there are new messages. Inside the SDK, the statuses of the badges are cached. If a request is made within 5 minutes after the last request is made, the result of the last successful request will be returned.
-
-
-
-```cs
-TapBillboard.QueryBadgeDetails((badgeDetails, error) =>
-{
- if (error != null)
- {
- // Failed to obtain badge statuses; see `error.code` and `error.errorDescription` for the reason
- }
- else
- {
- // Badge statuses obtained
- if (badgeDetails.showRedDot == 1) {
- // New messages found
- } else {
- // No new messages
- }
- }
-});
-```
-
-```java
-TapBillboard.getBadgeDetails(new Callback() {
- @Override
- public void onError(TapBillboardException tapBillboardException) {
- // Failed to obtain badge statuses; see `tapBillboardException.code` and `tapBillboardException.message` for the reason
- }
-
- @Override
- public void onSuccess(BadgeDetails badgeDetails) {
- if (badgeDetails.showRedDot == 1) {
- // New messages found
- } else {
- // No new messages
- }
- }
-});
-```
-
-```objectivec
-[TapBillboard getBadgeDetails:^(BadgeDetails * _Nullable result, NSError *_Nullable error) {
- if (error) {
- // Failed to obtain badge statuses; see `error.code` and `error.errorDescription` for the reason
- } else {
- if ([result.showRedDot intValue] == 1) {
- // New messages found
- } else {
- // No new messages
- }
- }
-}];
-```
-
-
-
-## Registering Custom Event Listeners
-
-If [an announcement is configured to jump to *a module in the game*](../features/#create-announcements), or if you have added links in the content of an announcement that jumps to *a module in the game*, you can obtain the message with a custom event listener you set up and handle the message accordingly.
-
-
-
-```cs
-TapBillboard.RegisterCustomLinkListener(url =>
-{
- // The URL returned here is the same as the one configured on the Developer Center
-});
-```
-
-```java
-TapBillboard.registerCustomLinkListener(new CustomLinkListener() {
- @Override
- public void onCustomUrlClick(String url) {
- // The URL returned here is the same as the one configured on the Developer Center
- // Please switch to the main thread if you need to update the UI
- }
-});
-```
-
-```objectivec
-[TapBillboard registerCustomLinkListener:^(NSString * _Nullable customUrl) {
- // The URL returned here is the same as the one configured on the Developer Center
-}];
-```
-
-
-
-## Unregistering Custom Event Listeners
-
-Use the following interface to stop listening to the custom events related to announcements:
-
-
-
-```cs
-// Registered listeners should be stored and provided here to have them unregistered
-TapBillboard.UnRegisterCustomLinkListener(registerdListener);
-```
-
-```java
-// Registered listeners should be stored and provided here to have them unregistered
-TapBillboard.unRegisterCustomLinkListener(registerdListener);
-```
-
-```objectivec
-// Registered listeners should be stored and provided here to have them unregistered
-[TapBillboard unRegisterCustomLinkListener:registerdListener];
-```
-
-
-
-## Internationalization
-
-You can set different languages for the Billboard service:
-
-
-
-## Error Codes
-
-| `code` | Scenario |
-|---|---|
-| 400 | An error exists in the parameters used for initializing the SDK. |
-| 403 | The user is not authorized to access this service. Please make sure the Billboard service has been enabled on the Developer Center. |
-| 50x | There is an internal error in our server. See `description` for more information. |
-| 19999 | An unknown error occurred. See `description` for more information. |
-
-## REST API
-
-Below are the REST APIs provided by the Billboard services.
-
-### Request Format
-
-For POST
-
-For POST and PUT requests, the request body must be in JSON and the Content-Type of the HTTP Header must be `application/json`.
-
-Requests are authenticated by the key-value pairs in the HTTP Header shown in the following table:
-
-| Key | Value | Description | Source |
-| ---------- | ------------ | ----------------------------------------- | -------------- |
-| `X-LC-Id` | `{{appid}}` | The `App Id` (`Client Id`) of the current app | Can be found on the Developer Center |
-| `X-LC-Sign` | `{{appSign}}` | A string in the form of `sign,timestamp`. `timestamp` is the unix timestamp (UTC) of the client in milliseconds. `sign` is a string formed by concatenating the `timestamp` with the `App Key` (`Client Token`) and running it through the MD5 algorithm.| See the algorithm below. |
-
-To calculate the `appSign` with the `App Key`:
-
-```sh
-md5( timestamp + App Key )
-= md5(1453014943466UtOCzqb67d3sN12Kts4URwy8)
-= d5bcbb897e19b2f6633c716dfdfaf9be
-
--H "X-LC-Sign: d5bcbb897e19b2f6633c716dfdfaf9be,1453014943466"
-```
-
-Besides setting the value of `X-LC-Id` to the `Client Id` in the HTTP Header, you will also need to provide the `Client Id` in the URL, and their values have to be the same.
-
-You will get an HTTP 400 error if something goes wrong, like:
-
-```json
-{
- "success": false,
- "data": {
- "code": 0,
- "error": "invalid_request",
- "msg": "invalid params",
- "error_description": ""
- },
- "now": 1659084246
-}
-```
-
-### Base URL
-
-The Base URL for REST API requests (the `{{host}}` in the curl examples) is the custom API domain of your app. You can update or find it on the Developer Center. See [Domain](/sdk/storage/guide/setup-dotnet#domain) for more details.
-
-### Obtaining the Template
-
-| Parameter | Constraint | Description |
-|---|---|---|
-| `client_id` | Required | The `Client ID` found on **Developer Center > Game Services > Configuration**. |
-| `template` | Required | The type of the template. For now, only Navigation Bar is supported. Use `navigate` for Navigation Bar and `image` for Poster. |
-
-```sh
-curl -X GET
- -H 'X-LC-Id: {{appid}}' \
- -H 'X-LC-Sign: {{appSign}}' \
- https://{{host}}/billboard/rest-api/v1/pattern/detail?client_id={{appid}}&template={{template}}
-```
-
-The response body is a JSON object containing all the parameters provided when the template is created:
-
-```json
-{
- "success": true,
- "data": {
- "empty_img": {
- "url": "https://tds-billboard.tds1.tapfiles.cn/20220727/aWkG63mpT2WeFrtT0Dxojj4QLfabWHh3.png"
- },
- "highlight_text_color": "#00D9C5",
- ...
- },
- "now": 1659085552
-}
-```
-
-### Obtaining Announcements
-
-| Parameter | Constraint | Description |
-|---|---|---|
-| `client_id` | Required | The `Client ID` found on **Developer Center > Game Services > Configuration**. |
-| `lang` | Required | See [Language Codes](#language-codes). |
-| `template` | Required | The type of the template. For now, only Navigation Bar is supported. Use `navigate` for Navigation Bar and `image` for Poster. |
-| `dimension_list` | Optional | `[{"PROPERTY_PARAMETER":"FIELD_PARAMETER"},...]`. Example: `[{"platform":"ios"},{"location":"CN"}]`. |
-| `type` | Required | The type of the announcement. Use `activity` for Events and `game` for Announcements.|
-| `uuid` | Optional | A unique ID identifying the device. Used for analytical purposes only. Example: `4e4105c7-781e-45c0-92ea-d595c75a3c2c`. |
-
-```sh
-curl -X POST
- -H 'X-LC-Id: {{appid}}' \
- -H 'X-LC-Sign: {{appSign}}' \
- -H 'Content-Type: application/json' \
- -d '{
- "lang":{{lang}},
- "template":{{template}},
- "type":{{type}},
- "dimension_list":{{dimension_list}}
- }' \
- https://{{host}}/billboard/rest-api/v1/announcement/list?client_id={{appid}}&uuid={{uuid}}
-```
-
-The returned JSON object contains the announcement list, `list`, as well as the details of the latest announcement, `lastest`.
-
-| Parameter | Description |
-|---|---|
-| `id` | The ID of the announcement. |
-| `type` | The type of the announcement. Could be `update`, `maintenance`, `new`, `activity`, `play_method`, `test`, or `discontinued`. |
-| `expire_time` | The timestamp of the expiration time in seconds. `0` means the announcement will not expire. |
-| `short_title` | The short title. |
-| `jump_location` | The place to jump to. Could be `content`, `link`, or `game`. |
-| `jump_link` | The link for redirection or the parameter indicating the module in the game to jump to. |
-| `publish_time` | The timestamp of the publication time in seconds. |
-
-```json
-{
- "success": true,
- "data": {
- "list": [
- {
- "id": 2,
- "type": "activity",
- "expire_time": 0,
- "short_title": "Hello",
- "jump_location": "link",
- "jump_link": "https://www.taptap.cn",
- "publish_time": 1659077524
- },
- ...
- ],
- "lastest": {
- "id": 2,
- "content": "This is the content.",
- "short_title": "Hello",
- "long_title": "Hello world!"
- }
- },
- "now": 1659085756
-}
-```
-
-### Obtaining Announcement Details
-
-| Parameter | Constraint | Description |
-|---|---|---|
-| `client_id` | Required | The `Client ID` found on **Developer Center > Game Services > Configuration**. |
-| `lang` | Required | See [Language Codes](#language-codes). |
-| `id` | Required | The ID of the announcement. |
-| `uuid` | Optional | A unique ID identifying the device. Used for analytical purposes only. Example: `4e4105c7-781e-45c0-92ea-d595c75a3c2c`. |
-
-```sh
-curl -X GET
- -H 'X-LC-Id: {{appid}}' \
- -H 'X-LC-Sign: {{appSign}}' \
- https://{{host}}/billboard/rest-api/v1/announcement/detail?client_id={{appid}}&lang={{lang}}&id={{id}}&uuid={{uuid}}
-```
-
-Returned JSON object
-
-| Parameter | Description |
-|---|---|
-| `id` | The ID of the announcement. |
-| `content` | The content of the announcement. |
-| `long_title` | The long title of the announcement. |
-| `short_title` | The short title of the announcement. |
-
-```json
-{
- "success": true,
- "data": {
- "id": 82,
- "content": "This is the content.",
- "short_title": "This is the short title.",
- "long_title": "This is the long title."
- },
- "now": 1659087990
-}
-```
-
-### Obtaining Badge Statuses
-
-| Parameter | Constraint | Description |
-|---|---|---|
-| `client_id` | Required | The `Client ID` found on **Developer Center > Game Services > Configuration**. |
-| `uuid` | Required | A unique ID identifying the device. Example: `4e4105c7-781e-45c0-92ea-d595c75a3c2c`. |
-| `template` | Required | The type of the template. For now, only Navigation Bar is supported. Use `navigate` for Navigation Bar and `image` for Poster. |
-| `dimension_list` | Optional | `[{"PROPERTY_PARAMETER":"FIELD_PARAMETER"},...]`. Example: `[{"platform":"ios"},{"location":"CN"}]`. |
-
-```sh
-curl -X POST \
- -H 'X-LC-Id: {{appid}}' \
- -H 'X-LC-Sign: {{appSign}}' \
- -H 'Content-Type: application/json' \
- -d '{
- "uuid": {{uuid}},
- "template":{{template}},
- "dimension_list":{{dimension_list}}
- }' \
- 'https://{{host}}/billboard/rest-api/v1/dot/read?client_id={{client_id}}'
-```
-
-Returned JSON object
-
-| Parameter | Description |
-|---|---|
-| `show_red_dot` | Whether to show the badge. `0` means no and `1` means yes. |
-| `close_button_img` | The URL of the image for the close button. |
-
-```json
-{
- "success": true,
- "data": {
- "show_red_dot": 0,
- "close_button_img": "https://tds-billboard.tds1.tapfiles.cn/20220727/aMHoqDTHT4zrXYPNprkZjXkdLK6vFg8E.png"
- },
- "now": 1658896487
-}
-```
-
-### Updating Badge Statuses
-
-| Parameter | Constraint | Description |
-|---|---|---|
-| `client_id` | Required | The `Client ID` found on **Developer Center > Game Services > Configuration**. |
-| `uuid` | Required | A unique ID identifying the device. Example: `4e4105c7-781e-45c0-92ea-d595c75a3c2c`. |
-| `read_all` | Required | Whether to mark all announcements as read. `true` for yes and `false` for no. |
-
-```sh
-curl -X POST \
- -H 'X-LC-Id: {{appid}}' \
- -H 'X-LC-Sign: {{appSign}}' \
- -H 'Content-Type: application/json' \
- -d '{
- "uuid": {{uuid}},
- "read_all":{{read_all}}
- }' \
- 'https://{{host}}/billboard/rest-api/v1/dot/submit?client_id={{client_id}}'
-```
-
-Returned JSON object
-```json
-{
- "success": true,
- "data": {
- "msg": "ok"
- },
- "now": 1658895192
-}
-```
-
-### Language Codes
-
-The REST API accepts language codes defined by ISO 639-1. For example, `en` means English and `jp` means Japanese. There are a couple of exceptions:
-
-1. For languages not included in ISO 639-1, the language codes defined in ISO 632-2 are used. For example, `fil` means Filipino.
-2. When a language cannot be represented by a language code, the location code defined in ISO 3166-1 will be attached. For example, `zh_CN` means Simplified Chinese.
-
-The following language codes are supported by the REST API:
-
-| Code | Language |
-| ------- | ----------- |
-| zh_CN | Simplified Chinese |
-| zh_TW | Traditional Chinese |
-| en_US | English (US) |
-| ja_JP | Japanese |
-| ko_KR | Korean |
-| pt_PT | Portuguese |
-| vi_VN | Vietnamese |
-| hi_IN | Hindi |
-| id_ID | Indonesian |
-| ms_MY | Malay |
-| th_TH | Thai |
-| es_ES | Spanish |
-| af | Afrikaans |
-| am | Amharic |
-| bg | Bulgarian |
-| ca | Catalan |
-| hr | Croatian |
-| cs | Czech |
-| da | Danish |
-| nl | Dutch |
-| et | Estonian |
-| fil | Filipino |
-| fi | Finnish |
-| fr | French |
-| de | German |
-| el | Greek |
-| he | Hebrew |
-| hu | Hungarian |
-| is | Icelandic |
-| it | Italian |
-| lv | Latvian |
-| lt | Lithuanian |
-| no | Norwegian |
-| pl | Polish |
-| ro | Romanian |
-| ru | Russian |
-| sr | Serbian |
-| sk | Slovak |
-| sl | Slovenian |
-| sw | Swahili |
-| sv | Swedish |
-| tr | Turkish |
-| uk | Ukrainian |
-| zu | Zulu |
-
-Keep in mind that some of the languages listed above are only supported by the REST API but [not the client SDK](#internationalization).
-
-## Video Tutorials
-
-You can refer to the video tutorial:[How to Integrate Billboard Functionality in Games](https://www.bilibili.com/video/BV1tp4y1L7Fe/) to learn how to access the billboard feature in Untiy project.
-
-For more video tutorials, see [Developer Academy](https://developer.taptap.cn/tds-tutorials/list). As the SDK features are constantly being improved, there may be inconsistencies between the video tutorials and the new SDK features, so the current documentation should prevail.
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/_category_.json
deleted file mode 100644
index e777c9648..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "游戏好友",
- "collapsed": true,
- "position": 6
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/features.mdx
deleted file mode 100644
index b4d7a6158..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/features.mdx
+++ /dev/null
@@ -1,123 +0,0 @@
----
-title: Friends Introduction
-sidebar_label: Introduction
-sidebar_position: 1
-slug: /sdk/friends/features
----
-
-
-
-
-
-
-## Introduction
-
-TapTap Developer Services (TDS) offers the solution for you to quickly add friending-related features into your game.
-
-## What We Offer
-
-- **Easy-to-use APIs that improve your productivity**
-
- The Friends service provides features like adding, deleting, and searching for friends so you don’t have to build them yourself.
-
-- **Real-time data tracking that allows you to perform refined operations**
-
- As a developer, you can utilize our tools to perform admin operations like looking up players’ connections and activity logs.
-
-- **Access to TapTap’s ecosystem**
-
- Obtain the connections among TapTap users and boost your game’s experience with them.
-
-## Basic Concepts
-
-**Game friends**
-
-Game friends are based on the built-in TDS account system. Interfaces applicable to them include adding, deleting, and searching for friends. There are two modes available for game friends: Follow mode and Friend mode.
-
-- Follow mode
-
- Player A can follow Player B to become their follower. Player B now becomes a followee of Player A. If Player B then follows Player A, Player A and Player B will become mutual followers.
-
-- Friend mode
-
- Player A can send a friend request to Player B. Once Player B accepts the request, Player A and Player B become friends.
-
-You can choose either Follow mode or Friend mode for your game, but not both.
-
-**TapTap friends**
-
-For users logged in with TapTap, you can obtain the mutual followers of these users from your game.
-
-:::tip
-Both game friends and TapTap friends depend on the built-in TDS account system. Make sure to enable TDS Authentication before you use Friends in your game.
-:::
-
-## Functions
-
-### Game Friends
-
-#### Follow Mode
-
-| **Function** | **Scenarios** |
-| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Get followee list | Players can view the other players they are following. |
-| Get mutual follower list | Players can view a list of their mutual followers. |
-| Get blocklist | Players can view the other players they have blocked. |
-| Follow | Players can follow other players from player lists or scoreboards. |
-| Unfollow | Players can unfollow other players. |
-| Block | Players can block other players from player lists or scoreboards. At this time, each player can have a maximum of 100 players on their blocklist. |
-| Unblock | Players can remove other players from their blocklist. |
-| Search and follow players | Players can search and follow other players with their nicknames, friend codes (a 6-digit code containing lowercase letters), and `objectId`s. |
-| Share follow invites | Players can share links for follow invites on their social media. |
-| Get follower list | Players can view the other players who are following them. |
-| Rich presence | Display a player’s status information like their online status, current hero, and current game mode. |
-| Sort by online status | A player’s followee list and mutual follower list can be sorted by online status. The follower list doesn’t support this function yet. |
-
-#### Friend Mode
-
-| **Function** | **Scenarios** |
-| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Get friend list | Players can view a list of their friends. |
-| Check friendship status | Check if any two players are friends. |
-| Add friends | Players can send friend requests to other players from player lists or scoreboards. Players can also view the friend requests they have received. |
-| Search and add friends | Players can search and send friend requests to other players with their nicknames, friend codes (a 6-digit code containing lowercase letters), and `objectId`s. |
-| Share friend invites | Players can share links for friend invites on their social media. |
-| Delete friends | Players can delete their friends. |
-| Get pending friend requests | Players can view the friend requests they have received. |
-| Accept friend requests | Players can accept other players’ requests to be their friends. |
-| Decline friend requests | Players can decline other players’ requests to be their friends. |
-| Get blocklist | Players can view the other players they have blocked. |
-| Block | Players can block other players from player lists or scoreboards. At this time, each player can have a maximum of 100 players on their blocklist. |
-| Unblock | Players can remove other players from their blocklist. |
-| Sort by online status | A player’s friend list can be sorted by online status. |
-| Set friend limit | You can set the maximum number of friends each player can have by going to **Developer Center > Game Services > Cloud Services > Friends**. The maximum value for this setting is 2000. |
-| Rich presence | Display a player’s status information like their online status, current hero, and current game mode. |
-
-### TapTap Friends
-
-| **Function** | **Scenarios** |
-| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Follow TapTap account (please contact us to enable this interface) | Players can follow other players’ TapTap accounts in your game. |
-| Get TapTap mutual followers (please contact us to enable this interface) | If two players are following each other and they both authorized your game to access their friend lists, your game will be able to obtain their connection. |
-
-## Pricing
-
-- You will be billed for the Friends service according to the number of API requests made. This number will be counted into the number of API requests made for the Data Storage service. The two services together share the free quota of 30,000 requests per day. $0.04 will be charged for every 1,000 requests exceeding the free quota.
-
-- Access to TapTap Friends is currently free. Please contact us to enable it.
-
-## Q&A
-
-Q: Can I use the Follow mode together with the Friend mode?
-
-A: No. You can only pick one of them for your game.
-
-Q: Is there a limit for the number of followees for the Follow mode?
-
-A: Yes. A player can follow at most 5000 people in each game. This number is not adjustable.
-
-Q: Can I customize the text for sharing invites?
-
-A: Yes. We recommend that you use something like “xx invited you to follow them” for the Follow mode and “xx invited you to become their friend” for the Friend mode.
-
-If you have any other questions, feel free to submit a ticket to us from the Developer Center.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/follow.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/follow.mdx
deleted file mode 100644
index ebdb59272..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/follow.mdx
+++ /dev/null
@@ -1,1046 +0,0 @@
----
-title: Follow Mode
-sidebar_position: 4
-slug: /sdk/friends/follow
----
-
-
-
-
-
-import MultiLang from "/src/docComponents/MultiLang";
-
-Before continuing, make sure you have [finished initializing the SDK](/sdk/friends/guide/).
-
-## Responding to Friend Status Changes
-
-With the Friends module, the client can listen to the status changes of the player’s friends and display them to the player in real-time.
-You’ll need to register an instance for listening to friend status changes before calling the interface for getting the player online. By doing this, the player will be able to receive notifications after they get online:
-
-
-
-```cs
-TDSFollows.FriendStatusChangedDelegate = new TDSFriendStatusChangedDelegate {
- // The current player got online (connection established)
- OnConnected = () => {},
- // Connection lost; the SDK will try to reconnect automatically
- OnDisconnected = () => {},
- // Connection error
- OnConnectionError = (code, message) => {},
-};
-```
-
-```java
-TDSFollows.registerFriendStatusChangedListener(new FriendStatusChangedListener() {
- // The current player got online (connection established)
- @Override
- public void onConnected() {}
-
- // Connection lost; the SDK will try to reconnect automatically
- @Override
- public void onDisconnected() {}
-
- // Connection error
- @Override
- public void onConnectError(int code, String msg){});
-}
-```
-
-```objc
-[TDSFollows registerNotificationDelegate:self];
-
-// The current player got online (connection established)
-- (void)onConnected {}
-
-// Connection lost; the SDK will try to reconnect automatically
-- (void)onDisconnected {}
-
-// Connection error
-- (void)onDisconnectedWithError:(NSError * _Nullable)error {}
-```
-
-
-
-To stop listening:
-
-
-
-```cs
-TDSFollows.FriendStatusChangedDelegate = null;
-```
-
-```java
-TDSFollows.removeFriendStatusChangedListener();
-```
-
-```objc
-[TDSFollows unregisterNotificationDelegate];
-```
-
-
-
-## Getting the Player Online
-
-After the player logs in, you need to call this interface to establish a persistent connection between the client and the cloud.
-Once the persistent connection is established, if there is an interruption to the internet connection, the SDK will automatically reconnect once the connection is restored.
-
-
-<>
-
-```cs
-await TDSFollows.Online();
-```
-
->
-<>
-
-```java
-TDSFollows.online(new Callback() {
- @Override
- public void onSuccess(Void result) {
- // Success
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- // Handle error
- }
-});
-
-```
-
-With the persistent connection established, if the player opens an invitation link, the Android SDK will automatically send a friend request to the corresponding player.
-
->
-<>
-
-```objc
-[TDSFollows online];
-```
-
->
-
-
-## Getting the Player Offline
-
-After the player logs out, you need to call this interface to disconnect the client from the cloud.
-
-
-
-```cs
-await TDSFollows.Offline();
-```
-
-```java
-TDSFollows.offline();
-```
-
-```objc
-[TDSFollows offline];
-```
-
-
-
-## Searching for Friends by Nickname
-
-A player can search for friends by nickname without knowing their `objectId`s.
-For example, to search for friends with `Tarara` as their nickname:
-
-
-
-```cs
-ReadOnlyCollection friendInfos = await TDSFollows.SearchUserByName("Tarara");
-foreach (TDSFriendInfo info in friendInfos) {
- // Player data
- TDSUser user = info.User;
- // Rich presence data; continue reading for more information
- Dictionary richPresence = RichPresence;
- // Whether the friend is online
- bool online = info.Online;
-}
-```
-
-```java
-TDSFollows.searchUserByName("Tarara", new ListCallback() {
- @Override
- public void onSuccess(List friendInfoList) {
- for (TDSFriendInfo info : friendInfoList) {
- // Player data
- TDSUser user = info.getUser();
- // Rich presence data; continue reading for more information
- TDSRichPresence richPresence = info.getRichPresence();
- // Whether the friend is online
- boolean online = info.isOnline();
- }
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- toast("Failed search friend by nickname" + error.detailMessage);
- }
-});
-```
-
-```objc
-TDSFriendQueryOption *option = [TDSFriendQueryOption new];
-option.from = 0;
-option.limit = 100;
-[TDSFollows searchUserWithNickname:@"Tarara" option:option
-callback:^(NSArray * _Nullable friendInfos, NSError * _Nullable error) {
- if (friendInfos) {
- for (TDSFriendInfo *info in friendInfos) {
- // Player data
- TDSUser *user = info.user;
- // Rich presence data; continue reading for more information
- NSDictionary *richPresence = info.richPresence;
- // Whether the friend is online
- BOOL online = info.online;
- }
- } else if (error) {
- // Handle error
- }
-}];
-```
-
-
-
-Notice that **in order to use this function, the `nickname` field has to be set on the built-in account system**.
-See [TDS Authentication Guide](/sdk/authentication/guide/#setting-other-user-properties) for more information.
-
-## Friend Code
-
-Each logged-in player has a friend code that can be shared with other players so these players can quickly add the current player as their friend.
-
-You can get the friend code of a `TDSUser` from its `shortId` field:
-
-
-
-```cs
-// currentUser is a logged in TDSUser
-string shortId = currentUser["shortId"];
-```
-
-```java
-String shortId = currentUser.getString("shortId");
-```
-
-```objc
-NSString *shortId = currentUser[@"shortId"];
-```
-
-
-
-To search for a player with their friend code:
-
-
-
-```cs
-TDSFriendInfo friendInfo = await TDSFollows.SearchUserByShortCode(shortId);
-```
-
-```java
-TDSFollows.searchUserByShortCode(shortId, new Callback() {
- @Override
- public void onSuccess(TDSFriendInfo friendInfo) { /* See the previous section */ }
-
- @Override
- public void onFail(TDSFriendError error) { /* See the previous section */ }
-});
-```
-
-```objc
-[TDSFollows searchUserWithShortCode:shortId
-callback:^(TDSFriendInfo * _Nullable friendInfo, NSError * _Nullable error) {
- // See the previous section
-}];
-```
-
-
-
-## Searching for Friends With `objectId`
-
-Besides using nicknames and friend codes, a player can also search for friends with their `objectId`s.
-
-For example, to search for the friend with `5b0b97cf06f4fd0abc0abe35` as their `objectId`:
-
-
-
-```cs
-TDSFriendInfo friendInfo = await TDSFollows.SearchUserById("5b0b97cf06f4fd0abc0abe35");
-```
-
-```java
-TDSFollows.searchUserById("5b0b97cf06f4fd0abc0abe35", new Callback() {
- @Override
- public void onSuccess(TDSFriendInfo friendInfo) { /* Other things to do */ }
-
- @Override
- public void onFail(TDSFriendError error) { /* Other things to do */ }
-});
-```
-
-```objc
-[TDSFollows searchUserWithObjectId:@"5b0b97cf06f4fd0abc0abe35"
-callback:^(TDSFriendInfo * _Nullable friendInfo, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-## Rich Presence
-
-Rich presence can be used to display the player’s status information like their online status, current hero, and current game mode.
-
-After adding configurations for rich presence on the Developer Center, you can set the content of a player’s rich presence according to the configured fields for rich presence:
-
-
-
-```cs
-await TDSFollows.SetRichPresence("score", "60");
-```
-
-```java
-TDSFollows.setRichPresence("score", "60", new Callback() {
- @Override
- public void onSuccess(Void result) {
- toast("Succeed to set rich presence.");
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- toast("Failed to set rich presence: " + error.detailMessage);
- }
-});
-```
-
-```objc
-[TDSFollows setRichPresenceWithKey:@"score" value:@"60"
- callback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- // Succeed to set rich presence.
- } else if (error) {
- // Failed to set rich presence.
- }
-}];
-```
-
-
-
-Here `score` is a rich presence field configured on the Developer Center.
-There are two types available for each rich presence field:
-
-- `variable`: The value is a string. In the code example above, with `score` configured to be a `variable` on the Developer Center, the client sets the value of this field to be `60` when updating the rich presence data. The cloud will accordingly return `"score": "60"` to the client as the rich presence data. You need to handle the localization-related logic yourself so that the player can eventually see something like “Score: 60”.
-
-- `token`: The value is a string starting with `#`. In the example below, the type of `display` is `token`, and the client sets the value of this field to be `#matching` when updating the rich presence data. The cloud will handle the conversion of this value to a localized string and return the result like `"display": "Matching"` to the client. Notice that if the cloud fails to convert the value, an empty string like `"display": " "` will be returned.
-
-To set multiple fields at once:
-
-
-
-```cs
-Dictionary info = new Dictionary();
-info.Add("score", "60");
-info.Add("display", "#matching");
-await TDSFollows.SetRichPresences(info);
-```
-
-```java
-Map info = new HashMap<>();
-info.put("score", "60");
-info.put("display", "#matching");
-TDSFollows.setRichPresence(info, new Callback() {
- // Other things to do
-});
-```
-
-```objc
-[TDSFollows setRichPresencesWithDictionary:@{
- @"score" : @"60",
- @"display" : @"#matching",
-} callback:^(BOOL succeeded, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-You can **configure at most 20 rich presence fields on the Developer Center**. The key of each field should be no longer than 128 bytes and the value of each field should be no longer than 256 bytes.
-
-You can use the following interface to clear a rich presence field for the current player:
-
-
-
-```cs
-TDSFollows.ClearRichPresence("score");
-```
-
-```java
-TDSFollows.clearRichPresence("score", new Callback() {
- // Other things to do
-});
-```
-
-```objc
-[TDSFollows clearRichPresenceWithKey:@"score"
-callback:^(BOOL succeeded, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-You can also clear multiple rich presence fields at once:
-
-
-
-```cs
-IEnumerable keys = new string[] {"score", "display"}
-await TDSFollows.ClearRichPresences(keys);
-```
-
-```java
-List keys = new ArrayList<>();
-keys.add("score");
-keys.add("display");
-TDSFollows.clearRichPresence(keys, new Callback() {
- // Other things to do
-});
-```
-
-```objc
-[TDSFollows clearRichPresencesWithKeys:@[@"score", @"display"]
-callback:^(BOOL succeeded, NSError * _Nullable error) {
- // Other things to do
-});
-```
-
-
-
-The interfaces for setting and clearing rich presence data can each be called at most once every 30 seconds.
-
-There are some [REST API interfaces](/sdk/friends/guide/#rich-presence-rest-api) related to rich presence.
-You can write your own scripts to perform administrative operations on the server side by interacting with these interfaces.
-
-## Following
-
-A player can follow other players by entering their [friend codes](/sdk/friends/mutual/#friend-code).
-
-
-
-```cs
-await TDSFollows.FollowByShortCode(shortId);
-```
-
-```java
-TDSFollows.followByShortCode(shortId, new Callback() {
- @Override
- public void onSuccess(HandleResult result) {
- // Followed
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- // Handle error
- }
-});
-```
-
-```objc
-[TDSFollows followWithShortCode:shortId callback:^(TDSFriendHandleResult * _Nullable result, NSError * _Nullable error) {
- if (error) {
- // handle error
- } else {
- // handle result
- }
-}];
-```
-
-
-
-Additional properties can be specified when following other players. For example, to put the other player into the `coworkers` group:
-
-
-
-```cs
-Dictionary attrs = new Dictionary {
- { "group", "coworkers" }
-};
-await TDSFollows.FollowByShortCode(shortId);
-```
-
-```java
-Map attrs = new HashMap();
-attrs.put("group", "coworkers");
-TDSFollows.followByShortCode(shortId, attrs, new Callback() {
- // Other things to do
-});
-```
-
-```objc
-NSDictionary *attributes = @{
- @"group" : @"coworkers",
-};
-[TDSFollows followWithShortCode:shortId attributes:attributes callback:^(TDSFriendHandleResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-A player can also follow a `TDSUser` with its `objectId`.
-For example, assuming Tarara’s `objectId` is `5b0b97cf06f4fd0abc0abe35`, the current player can follow Tarara with the following code:
-
-
-
-```cs
-await TDSFollows.Follow("5b0b97cf06f4fd0abc0abe35");
-```
-
-```java
-TDSFollows.follow("5b0b97cf06f4fd0abc0abe35", new Callback() {
- // Other things to do
-});
-```
-
-```objc
-[TDSFollows followWithUserId:@"5b0b97cf06f4fd0abc0abe35"
-callback:^(TDSFriendHandleResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-Additional properties can be specified as well when following other players with `objectId`:
-
-
-
-```cs
-Dictionary attrs = new Dictionary {
- { "group", "coworkers" }
-};
-await TDSFollows.Follow("5b0b97cf06f4fd0abc0abe35");
-```
-
-```java
-Map attrs = new HashMap();
-attrs.put("group", "coworkers");
-TDSFollows.follow("5b0b97cf06f4fd0abc0abe35", attrs, new Callback() {
- // Other things to do
-});
-```
-
-```objc
-NSDictionary *attributes = @{
- @"group" : @"coworkers",
-};
-[TDSFollows followWithUserId:@"5b0b97cf06f4fd0abc0abe35" attributes:attributes
-callback:^(TDSFriendHandleResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-Notice that a player can **follow at most 5000** other players.
-
-## Unfollowing
-
-A player can unfollow other players with their `objectId`s or friend codes.
-For example, after following Tarara, the current player changes their mind and doesn’t want to follow Tarara anymore:
-
-
-
-```cs
-await TDSFollows.UnFollow("5b0b97cf06f4fd0abc0abe35");
-
-await TDSFollows.UnFollowByShortCode(shortIdOfTarara);
-```
-
-```java
-TDSFollows.unfollow("5b0b97cf06f4fd0abc0abe35", new Callback() {
- @Override
- public void onSuccess(HandleResult result) {
- // Unfollowed
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- // Handle error
- }
-});
-
-TDSFollows.unfollowByShortCode(shortIdOfTarara, new Callback() {
- // Other things to do
-});
-```
-
-```objc
-[TDSFollows unfollowWithUserId:@"5b0b97cf06f4fd0abc0abe35" callback:^(TDSFriendHandleResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-
-[TDSFollows unfollowWithShortCode:shortIdOfTarara, callback:^(TDSFriendHandleResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-## Blocklist
-
-A player can block other players so they won’t be able to follow the current player anymore.
-By blocking a player, the existing connections between the current player and the target player will be removed.
-Similar to following and unfollowing, the player can block other players with their `objectId`s or friend codes.
-Assuming Tarara’s `objectId` is `5b0b97cf06f4fd0abc0abe35`, to block Tarara:
-
-
-
-```cs
-await TDSFollows.Block("5b0b97cf06f4fd0abc0abe35");
-
-await TDSFollows.BlockByShortCode(shortIdOfTarara);
-```
-
-```java
-TDSFollows.block("5b0b97cf06f4fd0abc0abe35", new Callback() {
- @Override
- public void onSuccess(HandleResult result) {
- // Blocked
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- // Handle error
- }
-});
-
-TDSFollows.blockByShortCode(shortIdOfTarara, new Callback() {
- // Other things to do
-});
-```
-
-```objc
-[TDSFollows blockWithObjectId:@"5b0b97cf06f4fd0abc0abe35" callback:^(TDSFriendHandleResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-
-[TDSFollows blockWithShortCode:shortIdOfTarara callback:^(TDSFriendHandleResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-A player can remove a user from their blocklist at any time:
-
-
-
-```cs
-await TDSFollows.Unblock("5b0b97cf06f4fd0abc0abe35");
-
-await TDSFollows.UnblockByShortCode(shortIdOfTarara);
-```
-
-```java
-TDSFollows.unblock("5b0b97cf06f4fd0abc0abe35", new Callback() {
- // Other things to do
-});
-
-TDSFollows.unblockByShortCode(shortIdOfTarara, new Callback() {
- // Other things to do
-});
-```
-
-```objc
-[TDSFollows unblockWithObjectId:@"5b0b97cf06f4fd0abc0abe35" callback:^(TDSFriendHandleResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-
-[TDSFollows unblockWithShortCode:shortIdOfTarara callback:^(TDSFriendHandleResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-If there have been any connections between the current player and a target player before the current player blocks the target player, **these connections will not be re-established automatically if the current player unblocks the target player**.
-
-To retrieve the current player’s blocklist:
-
-
-
-```cs
-FriendResult result = await TDSFollows.QueryBlockList(cursor, limit, sortCondition);
-```
-
-```java
-TDSFollows.queryBlockList(config, cursor, new Callback() {
- // Other things to do
-}
-```
-
-```objc
-[TDSFollows queryBlockListWithOption:option
-callback:^(TDSFriendResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-See [Retrieving Mutual Follower List](#retrieving-mutual-follower-list) for the meanings of the query parameters.
-
-
-
-Notice that:
-
-- A player can block at most 100 other players.
-- Once Player A blocks Player B, not only Player B cannot follow Player A, Player A cannot follow Player B as well. If Player A has followed Player B or Player B has followed Player A, once Player A blocks Player B, they won’t be following each other anymore.
-
-## Retrieving Mutual Follower List
-
-There is an interface for players to retrieve their mutual followers.
-This interface returns not only the mutual follower list but also a cursor.
-You can implement pagination by specifying the cursor and the number of players returned.
-The players in the result can be sorted according to their online status (those who are online will show up at the beginning).
-
-
-
-```cs
-// First query
-string cursor = null;
-// Defaults to 50; no larger than 500
-int limit = 50;
-// Sort by online status
-SortCondition sortCondition = SortCondition.OnlineCondition
-FriendResult result = await TDSFollows.QueryMutualList(cursor, limit, sortCondition);
-
-ReadOnlyCollection friendInfos = result.FriendList;
-foreach (TDSFriendInfo info in friendInfos) {
- // Player data
- TDSUser user = info.User;
- // Rich presence data
- Dictionary richPresence = info.RichPresence;
- // Whether the player is online
- bool online = info.Online;
-}
-
-// Pagination
-string cursor = result.Cursor;
-FriendResult more = await TDSFollows.QueryMutualList(cursor, limit, sortCondition);
-```
-
-```java
-FriendRequestConfig config = new FriendRequestConfig.Builder()
- .pageSize(50) /* Defaults to 50; no larger than 500 */
- .sortCondition(SortCondition.getOnlineCondition()) /* Sort by online status */
- .build();
-// First query
-TDSFollows.queryMutualList(config, null, new Callback() {
- @Override
- public void onSuccess(FriendResult result) {
- List friendInfoList = result.getFriendList();
- for (TDSFriendInfo info : friendInfoList) {
- // Player data
- TDSUser user = info.getUser();
- // Rich presence data
- TDSRichPresence richPresence = info.getRichPresence();
- // Whether the player is online
- boolean online = info.isOnline();
- }
-
- // Pagination
- String cursor = result.getCursor();
- TDSFollows.queryMutualList(config, cursor, new Callback() {
- /* Other things to do */
- }
- }
- @Override
- public void onFail(TDSFriendError error) {
- toast("query error = " + error.code + " msg = " + error.detailMessage);
- }
-});
-```
-
-```objc
-TDSFriendQueryOption *option = [TDSFriendQueryOption new];
-option.limit = 50;// Defaults to 50; no larger than 500
-__block NSString *cursor; // Cursor
-TDSFriendQuerySortCondition *sort = [TDSFriendQuerySortCondition new];
-[sort append:TDSFriendQuerySortTypeOnline error:nil];
-option.sortCondition = sort;
-
-[TDSFollows queryMutualListWithOption:option
-callback:^(TDSFriendResult * _Nullable result, NSError * _Nullable error) {
- for (TDSFriendInfo *info in result.friendList)
- // Player data
- TDSUser *user = info.user;
- // Rich presence data
- NSDictionary *richPresence = info.richPresence;
- // Whether the player is online
- BOOL online = info.online;
- }
- cursor = result.cursor;
-}];
-
-// Pagination
-option.from = cursor;
-[TDSFollows queryMutualListWithOption:option
-callback:^(TDSFriendResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-Notice that to make it possible for the server to sort players by online status, you have to call the corresponding interfaces when a player gets [online](#getting-the-player-online) and [offline](#getting-the-player-offline).
-Otherwise, the server won’t be able to know the online statuses of the players and won’t be able to sort them by online status.
-
-## Retrieving Followees
-
-Similarly, a player can retrieve the list of players they are following. The interface for this function is similar to that for retrieving mutual followers and the players in the result can be sorted by online status as well:
-
-
-
-```cs
-FriendResult result = await TDSFollows.QueryFolloweeList(cursor, limit, sortCondition);
-```
-
-```java
-TDSFollows.queryFolloweeList(config, cursor, new Callback() {
- // Other things to do
-}
-```
-
-```objc
-[TDSFollows queryFolloweeWithOption:option
-callback:^(TDSFriendResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-## Retrieving Followers
-
-A player can also retrieve the list of players who are following them. The interface for this function is similar to those for retrieving mutual followers and followees, but **it doesn’t support sorting the players in the result by online status**:
-
-
-
-```cs
-FriendResult result = await TDSFollows.QueryFollowerList(cursor, limit, sortCondition);
-```
-
-```java
-TDSFollows.queryFollowerList(config, cursor, new Callback() {
- // Other things to do
-}
-```
-
-```objc
-[TDSFollows queryFollowerWithOption:option
-callback:^(TDSFriendResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-If you specified to sort the result by online status, **the cloud will ignore this condition** and return the unsorted result.
-
-## Link Sharing
-
-### Landing Page
-
-A landing page has to be deployed before you use link sharing.
-The landing page can be deployed on [Cloud Engine](/sdk/engine/overview/) or any other server that can host a static page.
-If you plan to use Cloud Engine, keep in mind that the free instances provided by Cloud Engine come with auto-hibernation. Please consider purchasing standard instances.
-
-We provide an [open-source demo landing page] for you to use. You can build, deploy, and use it with your own configurations.
-Notice that the format of the `GAME_ANDROID_LINK` environment variable of the demo is `scheme://host/path`.
-The values of `host` and `path` should be consistent with those written in the `AndroidManifest.xml` of your Android project.
-
-[repo]: https://github.com/taptap/TapFriends-landing-page
-
-For example, if the `AndroidManifest.xml` of your project contains the following configurations:
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-```
-
-The value of `GAME_ANDROID_LINK` in your landing page should be `tapsdk://APP_ID/friends`.
-
-The address of the landing page should be configured in the client:
-
-```cs
-TDSFollows.SetShareLink("https://please-replace-with-your-domain.example.com");
-```
-
-If the landing page is hosted on Cloud Engine, the address will be `https://YOUR_CLOUD_ENGINE_CUSTOM_DOMAIN`.
-
-### Generating Invitation Links
-
-After the landing page has been deployed and the address has been configured in the client, you can call the following interface to generate invitation links:
-
-
-
-```cs
-string inviteUrl = await TDSFollows.GenerateFriendInvitationLink();
-```
-
-```java
-TDSFollows.generateFriendInvitationLink(new Callback() {
- @Override
- public void onSuccess(String inviteUrl) {
- System.out.println("share this link to invite your friends: " + inviteUrl);
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- System.out.println("Failed to generate invite link: " + error.detailMessage);
- }
-});
-```
-
-```objc
-NSError *error;
-NSString *inviteUrl = [TDSFollows generateFriendInvitationLinkWithError:&error];
-```
-
-
-
-The default username in the link will be the `nickname` of the player. Therefore, you might want to make sure that you have set the `nickname`s of the users in the built-in account system.
-See [TDS Authentication Guide](/sdk/authentication/guide/#setting-other-user-properties) for more information.
-To use other names, specify them when calling the above interface.
-You can also provide other parameters that can be attached to the URL of the invitation link as query parameters.
-For example, if the player named Tarara wants to use “Taro” as their name and attach a `ref=taptap` parameter, you can do this:
-
-
-
-```cs
-Dictionary parameters = new Dictionary {
- { "ref", "taptap" }
-};
-string inviteUrl = await TDSFollows.GenerateFriendInvitationLink("Taro", parameters);
-```
-
-```java
-Map parameters = new HashMap();
-parameters.put("ref", "taptap");
-TDSFollows.generateFriendInvitationLink("Taro", parameters, new Callback() {
- // Other things to do
-});
-```
-
-```objc
-NSError *error;
-TDSFriendLinkOption *option = [TDSFriendLinkOption new];
-option.roleName = @"Taro";
-option.queries = @{
- @"ref" : @"taptap",
-};
-NSString *inviteUrl = [TDSFollows generateFriendInvitationLinkWithOption:option error:&error];
-```
-
-
-
-### Handling Invitation Links
-
-After the player opens the game with an invitation link, you need to call the following interface to have the player follow the target player.
-
-
-
-```cs
-public class DeepLinkManager : MonoBehaviour
-{
- // Other things to do
- private async void onDeepLinkActivated(string url) {
- await TDSFollows.HandleFriendInvitationLink(url);
- }
-}
-```
-
-```java
-public void onHandleLink(View view) {
- TDSFollows.handFriendInvitationLink(url, new Callback() {
- @Override
- public void onSuccess(HandleResult result) {
- // Other things to do
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- // Other things to do
- }
- });
-}
-```
-
-```objc
-- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
- return [TDSFollows handleFriendInvitationLink:url
- callback:^(TDSFriendHandleResult * _Nullable result, TDSFriendsLinkInfo * _Nullable linkInfo, NSError * _Nullable error) {
- // Other things to do
- }];
-}
-```
-
-
-
-You can also parse the link with the following interface provided by the SDK and get the player’s `objectId` and name as well as other parameters. You can perform your custom logic with them.
-
-
-
-```cs
-public class DeepLinkManager : MonoBehaviour
-{
- // Other things to do
- private async void onDeepLinkActivated(string url) {
- TDSFriendLinkInfo invitation = TDSFollows.ParseFriendInvitationLink(url);
- string userObjectId = invitation.Identity;
- string name = invitation.RoleName;
- Dictionary parameters = invitation.Queries;
- await TDSFollows.Follow(userObjectId);
- }
-}
-```
-
-```java
-TDSFriendLinkInfo linkInfo = TDSFollows.parseFriendInvitationLink("url");
-String userObjectId = linkInfo.getIdentity();
-String name = linkInfo.getRoleName();
-Map parameters = linkInfo.getQueries();
-```
-
-```objc
-TDSFriendLinkInfo *linkInfo = [TDSFollows parseFriendInvitationLink:(NSURL *)url];
-NSString *userObjectId = linkInfo.identity;
-NSString *name = linkInfo.roleName;
-NSDictionary *parameters = linkInfo.queries;
-```
-
-
-
-Notice that:
-
-The demo project uses the Friend mode by default. Please change `INVITE_TYPE` to `follow` to switch to the Follow mode.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/guide.mdx
deleted file mode 100644
index 0e96c323a..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/guide.mdx
+++ /dev/null
@@ -1,316 +0,0 @@
----
-title: Friends Guide
-sidebar_label: Guide
-sidebar_position: 2
-slug: /sdk/friends/guide
----
-
-
-
-
-
-import MultiLang from "/src/docComponents/MultiLang";
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-
-The Friends service comes with two models:
-
-- [Friend mode](/sdk/friends/mutual/)
-- [Follow mode](/sdk/friends/follow/)
-
-Depending on the requirements of your game, you can pick one of the two models for your game.
-Keep in mind that:
-
-- You can only choose one of the two models for your game, **but not both**.
-- Once you have picked one model, **you cannot switch to the other one later**.
-
-Your game can also obtain players’ connections [from third-party platforms](/sdk/friends/third-party/). To use this feature, please submit a ticket to us to enable it.
-
-Here is a recommended path for you to learn how to use Friends in your game:
-
-- Learn about [TDS Authentication](/sdk/authentication/features/), the built-in TDS account system, which the Friends service depends on. The “players” and “users” mentioned later in this guide all refer to `TDSUser`s.
-
-- Follow this guide to learn about how to initialize the SDK.
-
-- Pick a model for your game depending on your requirements and read the corresponding guide:
-
- - [Friend mode](/sdk/friends/mutual/)
- - [Follow mode](/sdk/friends/follow/)
-
-## Initializing the SDK
-
-With the initialization process mentioned in [Quickstart](/sdk/start/quickstart/#initialization) completed, proceed to the [Downloads](/tap-download) page and download the TapSDK, then add the `TapFriends` module:
-
-
-
-
- {`"dependencies":{
- ...
- "com.taptap.tds.friends": "https://github.com/TapTap/TapFriends-Unity.git#${sdkVersions.taptap.unity}",
-}`}
-
-
-
- {`repositories{
- flatDir {
- dirs 'libs'
- }
-}
-dependencies {
- ...
- implementation (name:'TapFriend_${sdkVersions.taptap.android}', ext:'aar')
-}`}
-
-
-
- {`TapFriendResource.bundle
-TapFriendSDK.framework
-TapFriendUISDK.framework`}
-
-
-
-
-To use rich presence in your game, please first enable **Real-time synchronization of rich information interface data** on **Developer Center > Game Services > Cloud Services > Friends > Settings**,
-then set the rick presence fields needed through the REST API for [configuring rich presence fields](#configuring-rich-presence-fields).
-
-## Rich Presence REST API
-
-Below are the REST API interfaces related to rich presence.
-You can write your own scripts to perform administrative operations on the server side by interacting with these interfaces.
-
-### Request Format
-
-For POST and PUT requests, the request body must be in JSON, and the Content-Type of the HTTP Header should be `application/json`.
-
-Requests are authenticated by the following key-value pairs in the HTTP Header:
-
-| Key | Value | Meaning | Source |
-| ---------- | ------------ | ------------------------------------------- | ----------------------------------------- |
-| `X-LC-Id` | `{{appid}}` | The `App Id` (`Client Id`) of your game | Can be obtained from the Developer Center |
-| `X-LC-Key` | `{{appkey}}` | The `App Key` (`Client Token`) of your game | Can be obtained from the Developer Center |
-
-Using the management interface requires you to provide the `Master Key`: `X-LC-Key: {{masterkey}},master`.
-`Master Key`, also called `Server Secret`, can be obtained from the Developer Center as well.
-
-See [Credentials](/sdk/storage/guide/setup-dotnet#credentials) for more information.
-
-### Base URL
-
-The Base URL for REST API requests (the `{{host}}` in the curl examples) is the custom API domain of your app. You can update or find it on the Developer Center.
-See [Domain](/sdk/storage/guide/setup-dotnet#domain) for more details.
-
-### Retrieving Rich Presence Configurations
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: {{sessionToken}}" \
- https://{{host}}/friend/v1/rich-presence/config
-```
-
-Response:
-
-```json
-{
- "clientId": "YOUR CLIENT ID",
- "enabled": 1,
- "richMsgEnabled": true,
- "onlineMsgEnabled": true,
- "webSocketUrl": "wss://XXX.ws.tds1.tapapis.cn/ws/leancloud/v1",
- "richPresenceFields": [
- {
- "key": "display",
- "type": "token"
- },
- {
- "key": "leadboard",
- "type": "token"
- },
- {
- "key": "inviteable",
- "type": "variable"
- },
- {
- "key": "score",
- "type": "variable"
- },
- {
- "key": "rank",
- "type": "variable"
- }
- ],
- "richPresenceMultiLang": [
- {
- "key": "display",
- "lang": "en_US",
- "content": {
- "#playing": "Playing",
- "#idle": "Idle",
- "#room": "Room",
- "#matching": "Matching"
- }
- },
- {
- "key": "display",
- "lang": "zh_CN",
- "content": {
- "#playing": "游戏中",
- "#idle": "在线",
- "#room": "准备中",
- "#matching": "组队中"
- }
- },
- {
- "key": "leadboard",
- "lang": "en_US",
- "content": {
- "#score": "%score% score",
- "#rank": "%rank% rank"
- }
- },
- {
- "key": "leadboard",
- "lang": "zh_CN",
- "content": {
- "#score": "%score% 分,排名为 %rank%",
- "#rank": "%rank% 名"
- }
- }
- ]
-}
-```
-
-### Configuring Rich Presence Fields
-
-Rich presence fields can be set through the REST API. The names and types of each field needs to be provided.
-This is a management interface, so the `Master Key` is required for authentication:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{
- "enableRichMsg":true,
- "enableOnlineMsg":true,
- "richPresenceFields":[
- {"key":"display","type":"token"},
- {"key":"leadboard","type":"token"},
- {"key":"inviteable","type":"variable"},
- {"key":"score","type":"variable"},
- {"key":"rank","type":"variable"}
- ]
- }' \
- https://{{host}}/friend/v2/rich-presence/config/base-info
-```
-
-`enableRichMsg` and `enableOnlineMsg` are used to configure whether to enable notifications for rich presence updates and whether to enable notifications for friends’ online status updates. We recommend that you set both to be `true`.
-Notice that **the version in the URL for this interface is `v2`**, which is different from other interfaces that use `v1`.
-
-Response:
-
-```json
-{
- "appId": "YOUR CLIENT ID"
-}
-```
-
-Response when there is an error:
-
-```json
-{
- "code": 400,
- "error": "Missing request header"
-}
-```
-
-### Configuring Multi-Language Contents
-
-The following interface can be used to configure multi-language contents of rich presence fields.
-This is a management interface, so the `Master Key` is required for authentication:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{
- "action":"save",
- "config":[
- {
- "key": "display",
- "lang": "en_US",
- "content": {
- "#playing": "Playing",
- "#idle": "Idle",
- "#room": "Room",
- "#matching": "Matching"
- }
- },
- {
- "key": "display",
- "lang": "zh_CN",
- "content": {
- "#playing": "游戏中",
- "#idle": "在线",
- "#room": "准备中",
- "#matching": "组队中"
- }
- },
- {
- "key": "leadboard",
- "lang": "en_US",
- "content": {
- "#score": "%score% score",
- "#rank": "%rank% rank best"
- }
- },
- {
- "key": "leadboard",
- "lang": "zh_CN",
- "content": {
- "#score": "%score% 分,竞技排名为 %rank%",
- "#rank": "%rank% 名"
- }
- }
- ]
- }'
- https://{{host}}/friend/v1/rich-presence/config/lang-info
-```
-
-The response will be the same as the one for [configuring rich presence fields](#configuring-rich-presence-fields).
-
-### Retrieving Players’ Rich Presence Information
-
-You can retrieve the rich presence information of multiple players at once (provide a comma-separated `objectId` list):
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "X-LC-Session: {{sessionToken}}" \
- -G --data-urlencode 'ids={userObjectId,anotherUserObjectId}'
- https://{{host}}/friend/v1/rich-presence/users
-```
-
-Response
-
-```json
-{
- "results": [
- {
- "online": false,
- "richPresence": {
- "score": "15",
- "leadboard": "15 points; ranked 150th",
- "rank": "150"
- }
- },
- {
- "online": false,
- "richPresence": {}
- }
- ]
-}
-```
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/mutual.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/mutual.mdx
deleted file mode 100644
index 56b156102..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/mutual.mdx
+++ /dev/null
@@ -1,1380 +0,0 @@
----
-title: Friend Mode
-sidebar_position: 3
-slug: /sdk/friends/mutual
----
-
-
-
-
-
-import MultiLang from "/src/docComponents/MultiLang";
-
-Before continuing, make sure you have [finished initializing the SDK](/sdk/friends/guide/).
-
-## Responding to Friend Status Changes
-
-With the Friends module, the client can listen to the status changes of the player’s friends and display them to the player in real-time.
-You’ll need to register an instance for listening to friend status changes before calling the interface for getting the player online. By doing this, the player will be able to receive notifications after they get online:
-
-
-
-```cs
-TDSFriends.FriendStatusChangedDelegate = new TDSFriendStatusChangedDelegate {
- // New friend added (triggered together with “Sent friend request accepted”)
- OnFriendAdd = friendInfo => {},
- // New friend request received
- OnNewRequestComing = req => {},
- // Sent friend request accepted
- OnRequestAccepted = req => {},
- // Sent friend request declined
- OnRequestDeclined = req => {},
- // Friend got online
- OnFriendOnline = userId => {},
- // Friend got offline
- OnFriendOffline = userId => {},
- // Friend’s rich presence information changed
- OnRichPresenceChanged = (userId, richPresence) => {},
- // The current player got online (connection established)
- OnConnected = () => {},
- // Connection lost; the SDK will try to reconnect automatically
- OnDisconnected = () => {},
- // Connection error
- OnConnectionError = (code, message) => {},
-};
-```
-
-```java
-TDSFriends.registerFriendStatusChangedListener(new FriendStatusChangedListener() {
- // New friend added (triggered together with “Sent friend request accepted”)
- @Override
- public void onFriendAdd(TDSFriendInfo friendInfo) {}
-
- // New friend request received
- @Override
- public void onNewRequestComing(TDSFriendshipRequest request) {}
-
- // This callback will be triggered if the player entered the game with an invitation link
- // You can call handFriendInvitationLink within this callback
- // You can also parse the link with parseFriendInvitationLink, get the relevant parameters, and then perform your custom logic
- @Override
- public void onReceivedInvitationLink(String url) {}
-
- // Sent friend request accepted
- @Override
- public void onRequestAccepted(TDSFriendshipRequest request) {}
-
- // Sent friend request declined
- @Override
- public void onRequestDeclined(TDSFriendshipRequest request) {}
-
- // Friend got online
- @Override
- public void onFriendOnline(String userId) {}
-
- // Friend got offline
- @Override
- public void onFriendOffline(String userId) {}
-
- // Friend’s rich presence information changed
- @Override
- public void onRichPresenceChanged(String userId, TDSRichPresence richPresence) {}
-
- // The current player got online (connection established)
- @Override
- public void onConnected() {}
-
- // Connection lost; the SDK will try to reconnect automatically
- @Override
- public void onDisconnected() {}
-
- // Connection error
- @Override
- public void onConnectError(int code, String msg){});
-}
-```
-
-```objc
-[TDSFriends registerNotificationDelegate:self];
-
-// New friend added (triggered together with “Sent friend request accepted”)
-- (void)onFriendAdd:(TDSFriendInfo *)info {}
-
-// New friend request received
-- (void)onNewRequestComing:(TDSFriendshipRequest *)request {}
-
-// Sent friend request accepted
-- (void)onRequestAccepted:(TDSFriendshipRequest *)request {}
-
-// Sent friend request declined
-- (void)onRequestDeclined:(TDSFriendshipRequest *)request {}
-
-// Friend got online
-- (void)onFriendOnline:(NSString *)userId {}
-
-// Friend got offline
-- (void)onFriendOffline:(NSString *)userId {}
-
-// Friend’s rich presence information changed
-- (void)onRichPresenceChanged:(NSString *)userId dictionary:(NSDictionary * _Nullable)dictionary {}
-
-// The current player got online (connection established)
-- (void)onConnected {}
-
-// Connection lost; the SDK will try to reconnect automatically
-- (void)onDisconnected {}
-
-// Connection error
-- (void)onDisconnectedWithError:(NSError * _Nullable)error {}
-```
-
-
-
-The “friends” appearing in the events above refer to the friends under the Friend mode.
-The SDK doesn’t support listening to the events under the Follow mode.
-
-To stop listening:
-
-
-
-```cs
-TDSFriends.FriendStatusChangedDelegate = null;
-```
-
-```java
-TDSFriends.removeFriendStatusChangedListener();
-```
-
-```objc
-[TDSFriends unregisterNotificationDelegate];
-```
-
-
-
-## Getting the Player Online
-
-After the player logs in, you need to call this interface to establish a persistent connection between the client and the cloud.
-Once the persistent connection is established, if there is an interruption to the internet connection, the SDK will automatically reconnect once the connection is restored.
-
-
-<>
-
-```cs
-await TDSFriends.Online();
-```
-
->
-<>
-
-```java
-TDSFriends.online(new Callback() {
- @Override
- public void onSuccess(Void result) {
- // Success
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- // Handle error
- }
-});
-```
-
-With the persistent connection established, if the player opens an invitation link, the Android SDK will automatically send a friend request to the corresponding player.
-
->
-<>
-
-```objc
-[TDSFriends online];
-```
-
->
-
-
-## Getting the Player Offline
-
-After the player logs out, you need to call this interface to disconnect the client from the cloud.
-
-
-
-```cs
-await TDSFriends.Offline();
-```
-
-```java
-TDSFriends.offline();
-```
-
-```objc
-[TDSFriends offline];
-```
-
-
-
-## Searching for Friends by Nickname
-
-A player can search for friends by nickname without knowing their `objectId`s.
-For example, to search for friends with `Tarara` as their nickname:
-
-
-
-```cs
-ReadOnlyCollection friendInfos = await TDSFriends.SearchUserByName("Tarara");
-foreach (TDSFriendInfo info in friendInfos) {
- // Player data
- TDSUser user = info.User;
- // Rich presence data; continue reading for more information
- Dictionary richPresence = RichPresence;
- // Whether the friend is online
- bool online = info.Online;
-}
-```
-
-```java
-TDSFriends.searchUserByName("Tarara", new ListCallback() {
- @Override
- public void onSuccess(List friendInfoList) {
- for (TDSFriendInfo info : friendInfoList) {
- // Player data
- TDSUser user = info.getUser();
- // Rich presence data; continue reading for more information
- TDSRichPresence richPresence = info.getRichPresence();
- // Whether the friend is online
- boolean online = info.isOnline();
- }
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- toast("Failed search friend by nickname" + error.detailMessage);
- }
-});
-```
-
-```objc
-TDSFriendQueryOption *option = [TDSFriendQueryOption new];
-option.from = 0;
-option.limit = 100;
-[TDSFriends searchUserWithNickname:@"Tarara" option:option
-callback:^(NSArray * _Nullable friendInfos, NSError * _Nullable error) {
- if (friendInfos) {
- for (TDSFriendInfo *info in friendInfos) {
- // Player data
- TDSUser *user = info.user;
- // Rich presence data; continue reading for more information
- NSDictionary *richPresence = info.richPresence;
- // Whether the friend is online
- BOOL online = info.online;
- }
- } else if (error) {
- // Handle error
- }
-}];
-```
-
-
-
-Notice that **in order to use this function, the `nickname` field has to be set on the built-in account system**.
-See [TDS Authentication Guide](/sdk/authentication/guide/#setting-other-user-properties) for more information.
-
-## Friend Code
-
-Each logged-in player has a friend code that can be shared with other players so these players can quickly add the current player as their friend.
-
-You can get the friend code of a `TDSUser` from its `shortId` field:
-
-
-
-```cs
-// currentUser is a logged in TDSUser
-string shortId = currentUser["shortId"];
-```
-
-```java
-String shortId = currentUser.getString("shortId");
-```
-
-```objc
-NSString *shortId = currentUser[@"shortId"];
-```
-
-
-
-To search for a player with their friend code:
-
-
-
-```cs
-TDSFriendInfo friendInfo = await TDSFriends.SearchUserByShortCode(shortId);
-```
-
-```java
-TDSFriends.searchUserByShortCode(shortId, new Callback() {
- @Override
- public void onSuccess(TDSFriendInfo friendInfo) { /* See the previous section */ }
-
- @Override
- public void onFail(TDSFriendError error) { /* See the previous section */ }
-});
-```
-
-```objc
-[TDSFriends searchUserWithShortCode:shortId
-callback:^(TDSFriendInfo * _Nullable friendInfo, NSError * _Nullable error) {
- // See the previous section
-}];
-```
-
-
-
-## Searching for Friends With `objectId`
-
-Besides using nicknames and friend codes, a player can also search for friends with their `objectId`s.
-
-For example, to search for the friend with `5b0b97cf06f4fd0abc0abe35` as their `objectId`:
-
-
-
-```cs
-TDSFriendInfo friendInfo = await TDSFriends.SearchUserById("5b0b97cf06f4fd0abc0abe35");
-```
-
-```java
-TDSFriends.searchUserById("5b0b97cf06f4fd0abc0abe35", new Callback() {
- @Override
- public void onSuccess(TDSFriendInfo tdsFriendInfo) {
- /* See the previous section */
- }
- @Override
- public void onFail(TDSFriendError tdsFriendError) {
- /* See the previous section */
- }
-});
-```
-
-```objc
-[TDSFriends searchUserWithObjectId:@"5b0b97cf06f4fd0abc0abe35"
-callback:^(TDSFriendInfo * _Nullable friendInfo, NSError * _Nullable error) {
- // See the previous section
-}];
-```
-
-
-
-## Rich Presence
-
-Rich presence can be used to display the player’s status information like their online status, current hero, and current game mode.
-
-After adding configurations for rich presence on the Developer Center, you can set the content of a player’s rich presence according to the configured fields for rich presence:
-
-
-
-```cs
-await TDSFriends.SetRichPresence("score", "60");
-```
-
-```java
-TDSFriends.setRichPresence("score", "60", new Callback() {
- @Override
- public void onSuccess(Void result) {
- toast("Succeed to set rich presence.");
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- toast("Failed to set rich presence: " + error.detailMessage);
- }
-});
-```
-
-```objc
-[TDSFriends setRichPresenceWithKey:@"score" value:@"60"
- callback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- // Succeed to set rich presence.
- } else if (error) {
- // Failed to set rich presence.
- }
-}];
-```
-
-
-
-Here `score` is a rich presence field configured on the Developer Center.
-There are two types available for each rich presence field:
-
-- `variable`: The value is a string. In the code example above, with `score` configured to be a `variable` on the Developer Center, the client sets the value of this field to be `60` when updating the rich presence data. The cloud will accordingly return `"score": "60"` to the client as the rich presence data. You need to handle the localization-related logic yourself so that the player can eventually see something like “Score: 60”.
-
-- `token`: The value is a string starting with `#`. In the example below, the type of `display` is `token`, and the client sets the value of this field to be `#matching` when updating the rich presence data. The cloud will handle the conversion of this value to a localized string and return the result like `"display": "Matching"` to the client. Notice that if the cloud fails to convert the value, an empty string like `"display": " "` will be returned.
-
-To set multiple fields at once:
-
-
-
-```cs
-Dictionary info = new Dictionary();
-info.Add("score", "60");
-info.Add("display", "#matching");
-await TDSFriends.SetRichPresences(info);
-```
-
-```java
-Map info = new HashMap<>();
-info.put("score", "60");
-info.put("display", "#matching");
-TDSFriends.setRichPresence(info, new Callback() {
- // Other things to do
-});
-```
-
-```objc
-[TDSFriends setRichPresencesWithDictionary:@{
- @"score" : @"60",
- @"display" : @"#matching",
-} callback:^(BOOL succeeded, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-You can **configure at most 20 rich presence fields on the Developer Center**. The key of each field should be no longer than 128 bytes and the value of each field should be no longer than 256 bytes.
-
-You can use the following interface to clear a rich presence field for the current player:
-
-
-
-```cs
-TDSFriends.ClearRichPresence("score");
-```
-
-```java
-TDSFriends.clearRichPresence("score", new Callback() {
- // Other things to do
-});
-```
-
-```objc
-[TDSFriends clearRichPresenceWithKey:@"score"
-callback:^(BOOL succeeded, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-You can also clear multiple rich presence fields at once:
-
-
-
-```cs
-IEnumerable keys = new string[] {"score", "display"}
-await TDSFriends.ClearRichPresences(keys);
-```
-
-```java
-List keys = new ArrayList<>();
-keys.add("score");
-keys.add("display");
-TDSFriends.clearRichPresence(keys, new Callback() {
- // Other things to do
-});
-```
-
-```objc
-[TDSFriends clearRichPresencesWithKeys:@[@"score", @"display"]
-callback:^(BOOL succeeded, NSError * _Nullable error) {
- // Other things to do
-});
-```
-
-
-
-The interfaces for setting and clearing rich presence data can each be called at most once every 30 seconds.
-
-There are some [REST API interfaces](/sdk/friends/guide/#rich-presence-rest-api) related to rich presence.
-You can write your own scripts to perform administrative operations on the server side by interacting with these interfaces.
-
-## Adding Friends
-
-A player can add friends by entering their [friend codes](/sdk/friends/mutual/#friend-code).
-
-
-
-```cs
-await TDSFriends.AddFriendByShortCode(shortId);
-```
-
-```java
-TDSFriends.addFriendByShortCode(shortId, null, new Callback() {
- @Override
- public void onSuccess(Void result) {
- toast("Applied or added.");
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- toast("Failed to add a friend: " + error.detailMessage);
- }
-});
-```
-
-```objc
-[TDSFriends addFriendWithShortCode:shortId callback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- // Applied or added.
- } else if (error) {
- // Failed to add a friend.
- }
-}];
-```
-
-
-
-If the current player is already on the friend list of the other player, they will immediately become friends.
-Otherwise, a friend request will be sent to the other player.
-
-Additional properties can be specified when adding friends. For example, to put the other player into the `coworkers` group:
-
-
-
-```cs
-Dictionary attrs = new Dictionary {
- { "group", "coworkers" }
-};
-await TDSFriends.AddFriendByShortCode(shortId, attrs);
-```
-
-```java
-Map attrs = new HashMap();
-attrs.put("group", "coworkers");
-TDSFriends.addFriendByShortCode(shortId, attrs, new Callback() {
- // See the example above
-});
-```
-
-```objc
-NSDictionary *attributes = @{
- @"group" : @"coworkers",
-};
-[TDSFriends addFriendWithShortCode:shortId attributes:attributes
-callback:^(BOOL succeeded, NSError * _Nullable error) {
- // See the example above
-}];
-```
-
-
-
-A player can also add a `TDSUser` as their friend with its `objectId`.
-For example, assuming Tarara’s `objectId` is `5b0b97cf06f4fd0abc0abe35`, the current player can add Tarara as their friend with the following code:
-
-
-
-```cs
-await TDSFriends.AddFriend("5b0b97cf06f4fd0abc0abe35");
-```
-
-```java
-TDSFriends.addFriend("5b0b97cf06f4fd0abc0abe35", new Callback() {
- // See the example above
-});
-```
-
-```objc
-[TDSFriends addFriendWithUserId:@"5b0b97cf06f4fd0abc0abe35"
-callback:^(BOOL succeeded, NSError * _Nullable error) {
- // See the example above
-}];
-```
-
-
-
-Additional properties can be specified as well when adding friends with `objectId`:
-
-
-
-```cs
-Dictionary attrs = new Dictionary {
- { "group", "coworkers" }
-};
-await TDSFriends.AddFriend("5b0b97cf06f4fd0abc0abe35", attrs);
-```
-
-```java
-Map attrs = new HashMap();
-attrs.put("group", "coworkers");
-TDSFriends.addFriend("5b0b97cf06f4fd0abc0abe35", attrs, new Callback() {
- // See the example above
-});
-```
-
-```objc
-NSDictionary *attributes = @{
- @"group" : @"coworkers",
-};
-[TDSFriends addFriendWithUserId:@"5b0b97cf06f4fd0abc0abe35" attributes:attributes callback:^(BOOL succeeded, NSError * _Nullable error) {
- // See the example above
-}];
-```
-
-
-
-## Deleting Friends
-
-A player can delete their existing friends.
-For example, after becoming friends with Tarara, the current player changes their mind and doesn’t want to be friends with Tarara anymore:
-
-
-
-```cs
-await TDSFriends.DeleteFriend("5b0b97cf06f4fd0abc0abe35");
-```
-
-```java
-TDSFriends.deleteFriend("5b0b97cf06f4fd0abc0abe35", new Callback() {
- @Override
- public void onSuccess(Boolean ok) {
- toast("Deleted.");
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- toast("Failed to delete a friend: " + error.detailMessage);
- }
-});
-```
-
-```objc
-[TDSFriends deleteFriendWithUserId:@"5b0b97cf06f4fd0abc0abe35" callback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- // Deleted.
- } else if (error) {
- // Failed to delete a friend.
- }
-}];
-```
-
-
-
-## Blocklist
-
-### Blocking Users
-
-A player can block a user no matter if they’re friends or not. Once a player blocks a user, the ongoing friend requests between them will be deleted, and they won’t be able to send friend requests to each other anymore. Blocked users won’t appear in search results when the player searches for friends. A player can have at most 100 users in their blocklist.
-
-Assuming Tarara’s `objectId` is `5b0b97cf06f4fd0abc0abe35`, to block Tarara:
-
-
-<>
-
-```cs
-await TDSFriends.BlockFriend("5b0b97cf06f4fd0abc0abe35");
-```
-
->
-<>
-
-```java
-TDSFriends.blockFriend("5b0b97cf06f4fd0abc0abe35", new Callback(){
- @Override
- public void onSuccess(Void result) {
- // block user succeed.
- }
- @Override
- public void onFail(TDSFriendError error) {
- // Failed to block.
- }
-});
-```
-
->
-<>
-
-```objc
-[TDSFriends blockFriendWithUserId:@"5b0b97cf06f4fd0abc0abe35" callback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- // block user succeed.
- } else if (error) {
- // Failed to block.
- }
-}];
-```
-
->
-
-
-
-### Unblocking Users
-
-A player can remove a user from their blocklist at any time. If the user used to be a friend of the current player, the user will be added back to the friend list of the current player.
-
-
-<>
-
-```cs
-await TDSFriends.UnblockFriend("5b0b97cf06f4fd0abc0abe35");
-```
-
->
-<>
-
-```java
-TDSFriends.unblockFriend("5b0b97cf06f4fd0abc0abe35", new Callback() {
- @Override
- public void onSuccess(Void result) {
- // unblock succeed.
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- // Failed to unblock.
- }
-});
-```
-
->
-<>
-
-```objc
-[TDSFriends unblockFriendWithUserId:@"5b0b97cf06f4fd0abc0abe35" callback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- // unblock succeed.
- } else if (error) {
- // Failed to unblock.
- }
-}];
-```
-
->
-
-
-
-### Retrieving the Blocklist
-
-Use the code below to retrieve the current player’s blocklist with pagination.
-
-
-<>
-
-```cs
-var from = 0;
-var limit = 100;
-ReadOnlyCollection friendInfos = await TDSFriends.QueryBlockList(from, limit);
-foreach (TDSFriendInfo info in friendInfos) {
- // Player data
- TDSUser user = info.User;
- // Rich presence data
- Dictionary richPresence = info.RichPresence;
- // Whether the friend is online
- bool online = info.Online;
-}
-```
-
-In the code above:
-
-- `from` is the query’s offset. It will be `0` for the first page. For the next page, it will be the number of items retrieved from the last query.
-- `limit` is the number of items in each page.
-
->
-<>
-
-```java
-TDSFriends.queryBlockList(0, 10, new ListCallback() {
- @Override
- public void onSuccess(List result) {
- System.out.println("query blockList data, data = " + result);
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- System.out.println("query blockList failed, error = " + error);
- }
-});
-```
-
-In the code above:
-
-- `from` is the query’s offset. It will be `0` for the first page. For the next page, it will be the number of items retrieved from the last query.
-- `limit` is the number of items in each page.
-- `callback` is the callback for handling the result asynchronously. The result contains the users in the blocklist.
-
->
-<>
-
-```objc
-TDSFriendQueryOption *option = [TDSFriendQueryOption new];
-option.from = 0;
-option.limit = 100;
-[TDSFriends queryBlockListWithOption:option
- callback:^(NSArray * _Nullable friendInfos, NSError * _Nullable error) {
- if (friendInfos) {
- for (TDSFriendInfo *info in friendInfos) {
- // Player data
- TDSUser *user = info.user;
- // Rich presence data
- NSDictionary *richPresence = info.richPresence;
- // Whether the friend is online
- BOOL online = info.online;
- }
- } else if (error) {
- // Handle error
- }
-}];
-```
-
-In the code above:
-
-- `option` is the conditions for the query, including `from` for the offset and `limit` for the number of items.
-- `callback` is the callback for handling the result asynchronously.
-
->
-
-
-
-## Retrieving Friend Requests
-
-There are three possible statuses for each friend request:
-
-- `pending`: The target user hasn’t responded yet. This is the default status once a friend request has been created.
-- `accepted`: The target user accepted the request and they are already friends with the current player.
-- `declined`: The target user declined the request.
-
-The SDK offers an interface for retrieving friend requests.
-For example, to retrieve the first 20 requests with their status being `pending`:
-
-
-<>
-
-```cs
-var from = 0;
-var limit = 100;
-ReadOnlyCollection requests = await TDSFriends.QueryFriendRequestList (
- LCFriendshipRequest.STATUS_PENDING, from, limit
-);
-```
-
-`LCFriendshipRequest.STATUS_PENDING` means the status of the friend request is `pending`.
-Similarly, `LCFriendshipRequest.STATUS_ACCEPTED` means `accepted` and `LCFriendshipRequest.STATUS_DECLINED` means `declined`.
-`LCFriendshipRequest.STATUS_ANY` means any status.
-
->
-<>
-
-```java
-int from = 0;
-int limit = 100;
-TDSFriends.queryFriendRequestList(LCFriendshipRequest.STATUS_PENDING, from, limit,
- new ListCallback(){
-
- @Override
- public void onSuccess(List requests) {
- // requests is the list of pending friend requests
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- toast("Failed to query friendship requests: " + error.detailMessage);
- }
-});
-```
-
-In the example above, `LCFriendshipRequest.STATUS_PENDING` means the status of the friend request is `pending`.
-Similarly, `LCFriendshipRequest.STATUS_ACCEPTED` means `accepted` and `LCFriendshipRequest.STATUS_DECLINED` means `declined`.
-`LCFriendshipRequest.STATUS_ANY` means any status.
-
->
-<>
-
-```objc
-TDSFriendQueryOption *option = [TDSFriendQueryOption new];
-option.from = 0;
-option.limit = 100;
-[TDSFriends queryFriendRequestWithStatus:TDSUserFriendshipRequestStatusPending
- option:option
- callback:^(NSArray * _Nullable requests, NSError * _Nullable error) {
- // requests is the list of pending friend requests
-}];
-```
-
-In the example above, `TDSUserFriendshipRequestStatusPending` means the status of the friend request is `pending`.
-Similarly, `TDSUserFriendshipRequestStatusAccepted` means `accepted` and `TDSUserFriendshipRequestStatusDeclined` means `declined`.
-`TDSUserFriendshipRequestStatusAny` means any status.
-
->
-
-
-
-Use the following interface to include the rich presence data of the initiator of each request in the result:
-
-
-
-```cs
-ReadOnlyCollection requests = await TDSFriends.QueryFriendRequestWithFriendStateList (
- LCFriendshipRequest.STATUS_PENDING, from, limit
-);
-foreach (TDSFriendshipRequest request in requests) {
- // Friend request (see QueryFriendRequestList)
- LCFriendshipRequest req = request.FriendshipRequest;
- // The rich presence data of the initiator
- TDSFriendInfo info = request.FriendInfo;
-}
-```
-
-```java
-TDSFriends.queryFriendRequestWithFriendStateList(LCFriendshipRequest.STATUS_PENDING,
- from, limit, new ListCallback() {
- @Override
- public void onSuccess(List requests) {
- for (TDSFriendshipRequest request : requests) {
- // Friend request (see queryFriendRequestList)
- LCFriendshipRequest req = request.getLcFriendshipRequest();
- // The rich presence data of the initiator
- TDSFriendInfo info = request.getFriendInfo();
- }
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- // Handle error
- }
-});
-```
-
-```objc
-TDSFriendQueryOption *option = [TDSFriendQueryOption new];
-option.from = 0;
-option.limit = 100;
-[TDSFriends queryFriendRequestAndStateWithStatus:TDSUserFriendshipRequestStatusPending
- option:option
- callback:^(NSArray * _Nullable requests, NSError * _Nullable error) {
- for (TDSFriendshipRequest *request in requests) {
- // Friend request (see queryFriendRequestWithStatus)
- LCFriendshipRequest *req = request.lcFriendshipRequest;
- // The rich presence data of the initiator
- TDSFriendInfo *info = request.friendInfo;
- }
-}];
-```
-
-
-
-## Handling Friend Requests
-
-For each new friend request, the player can accept or decline it. They can also ignore the request or even delete it.
-
-
-
-```cs
-// LCFriendshipRequest request
-
-// Accept
-await TDSFriends.AcceptFriendshipRequest(request);
-// Accept and add additional attributes
-Dictionary attrs = new Dictionary {
- { "group", "coworkers" }
-};
-await TDSFriends.AcceptFriendshipRequest(request, attrs);
-
-// Decline
-await TDSFriends.DeclineFriendshipRequest(request);
-// Delete
-await request.Delete();
-```
-
-```java
-// LCFriendshipRequest request
-
-// Accept
-TDSFriends.acceptFriendRequest(request, new Callback() {
- @Override
- public void onSuccess(Void result) {
- toast("Accepted.");
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- toast("Failed to delete a friend: " + error.detailMessage);
- }
-});
-// Accept and add additional attributes
-Map attrs = new HashMap();
-attrs.put("group", "coworkers");
-TDSFriends.acceptFriendRequest(request, attrs, new Callback() {
- // Other things to do
-});
-
-// Decline
-TDSFriends.declineFriendRequest(request, new Callback() {
- // Other things to do
-});
-// Delete
-TDSFriends.deleteFriendRequest(request, new Callback() {
- // Other things to do
-});
-```
-
-```objc
-// LCFriendshipRequest request
-
-// Accept
-[TDSFriends acceptFriendRequest:request attributes:nil
-callback:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- // Accepted.
- } else if (error) {
- // Failed to accept a friend request.
- }
-}];
-// Accept and add additional attributes
-NSDictionary *attributes = @{
- @"group" : @"coworkers",
-};
-[TDSFriends acceptFriendRequest:request attributes:attributes
-callback:^(BOOL succeeded, NSError * _Nullable error) {
- // Other things to do
-}];
-
-// Decline
-[TDSFriends declineFriendRequest:request
-callback:^(BOOL succeeded, NSError * _Nullable error) {
- // Other things to do
-}];
-
-[TDSFriends deleteFriendRequest:request
-callback:^(BOOL succeeded, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-Note:
-
-1. If a user declines a friend request from the current player, the player will get an error when they try to send another request to the same user.
-2. If a user deletes a friend request from the current user, the player will be able to send another request to the same user.
-
-## Retrieving Friend List
-
-A player can retrieve their own friend list. When performing this operation, a limit and offset can be provided:
-
-
-
-```cs
-var from = 0;
-var limit = 100;
-ReadOnlyCollection friendInfos = await TDSFriends.QueryFriendList(from, limit);
-foreach (TDSFriendInfo info in friendInfos) {
- // Player data
- TDSUser user = info.User;
- // Rich presence data
- Dictionary richPresence = info.RichPresence;
- // Whether the friend is online
- bool online = info.Online;
-}
-```
-
-```java
-int from = 0;
-int limit = 100;
-TDSFriends.queryFriendList(from, limit,
- new ListCallback(){
-
- @Override
- public void onSuccess(List friendInfoList) {
- for (TDSFriendInfo info : friendInfoList) {
- // Player data
- TDSUser user = info.getUser();
- // Rich presence data
- TDSRichPresence richPresence = info.getRichPresence();
- // Whether the friend is online
- boolean online = info.isOnline();
- }
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- toast("Failed to query friend list" + error.detailMessage);
- }
-});
-```
-
-```objc
-TDSFriendQueryOption *option = [TDSFriendQueryOption new];
-option.from = 0;
-option.limit = 100;
-[TDSFriends queryFriendWithOption:option
- callback:^(NSArray * _Nullable friendInfos, NSError * _Nullable error) {
- if (friendInfos) {
- for (TDSFriendInfo *info in friendInfos) {
- // Player data
- TDSUser *user = info.user;
- // Rich presence data
- NSDictionary *richPresence = info.richPresence;
- // Whether the friend is online
- BOOL online = info.online;
- }
- } else if (error) {
- // Handle error
- }
-}];
-```
-
-
-
-## Check if a User Is a Friend
-
-You can check if a `TDSUser` is a friend of the current player with its `objectId`.
-For example, assuming Tarara’s `objectId` is `5b0b97cf06f4fd0abc0abe35`:
-
-
-
-```cs
-bool isFriend = await TDSFriends.CheckFriendship("5b0b97cf06f4fd0abc0abe35");
-```
-
-```java
-TDSFriends.checkFriendship("5b0b97cf06f4fd0abc0abe35", new Callback() {
- @Override
- public void onSuccess(Boolean isFriend) {
- if (isFriend) {
- toast("Tarara is my friend.");
- } else {
- toast("Tarara is not my friend.");
- }
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- toast("Failed to query friendship: " + error.detailMessage);
- }
-});
-```
-
-```objc
-[TDSFriends checkFriendshipWithUserId:@"5b0b97cf06f4fd0abc0abe35"
- callback:^(NSNumber * _Nullable isFriend, NSError * _Nullable error) {
- if (error) {
- // Handle error
- }
- if (isFriend.boolValue) {
- NSLog(@"Tarara is my friend.");
- } else {
- NSLog(@"Tarara is not my friend.");
- }
-}];
-```
-
-
-
-## Link Sharing
-
-### Landing Page
-
-A landing page has to be deployed before you use link sharing.
-The landing page can be deployed on [Cloud Engine](/sdk/engine/overview/) or any other server that can host a static page.
-If you plan to use Cloud Engine, keep in mind that the free instances provided by Cloud Engine come with auto-hibernation. Please consider purchasing standard instances.
-
-We provide an [open-source demo landing page] for you to use. You can build, deploy, and use it with your own configurations.
-Notice that the format of the `GAME_ANDROID_LINK` environment variable of the demo is `scheme://host/path`.
-The values of `host` and `path` should be consistent with those written in the `AndroidManifest.xml` of your Android project.
-
-[repo]: https://github.com/taptap/TapFriends-landing-page
-
-For example, if the `AndroidManifest.xml` of your project contains the following configurations:
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-```
-
-The value of `GAME_ANDROID_LINK` in your landing page should be `tapsdk://APP_ID/friends`.
-
-The address of the landing page should be configured in the client:
-
-
-
-```cs
-TDSFriends.SetShareLink("https://please-replace-with-your-domain.example.com");
-```
-
-```java
-TDSFriends.setShareLink("https://please-replace-with-your-domain.example.com");
-```
-
-```objc
-[TDSFriends setShareLink:@"https://please-replace-with-your-domain.example.com"];
-```
-
-
-
-If the landing page is hosted on Cloud Engine, the address will be `https://YOUR_CLOUD_ENGINE_CUSTOM_DOMAIN`.
-
-### Generating Invitation Links
-
-After the landing page has been deployed and the address has been configured in the client, you can call the following interface to generate invitation links:
-
-
-
-```cs
-string inviteUrl = await TDSFriends.GenerateFriendInvitationLink();
-```
-
-```java
-TDSFriends.generateFriendInvitationLink(new Callback() {
- @Override
- public void onSuccess(String inviteUrl) {
- System.out.println("share this link to invite your friends: " + inviteUrl);
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- System.out.println("Failed to generate invite link: " + error.detailMessage);
- }
-});
-```
-
-```objc
-NSError *error;
-NSString *inviteUrl = [TDSFriends generateFriendInvitationLinkWithError:&error];
-```
-
-
-
-The default username in the link will be the `nickname` of the player.
-Therefore, you might want to make sure that you have set the `nickname`s of the users in the built-in account system.
-See [TDS Authentication Guide](/sdk/authentication/guide/#setting-other-user-properties) for more information.
-To use other names, specify them when calling the above interface.
-You can also provide other parameters that can be attached to the URL of the invitation link as query parameters.
-For example, if the player named Tarara wants to use “Taro” as their name and attach a `ref=taptap` parameter, you can do this:
-
-
-
-```cs
-Dictionary parameters = new Dictionary {
- { "ref", "taptap" }
-};
-string inviteUrl = await TDSFriends.GenerateFriendInvitationLink("Taro", parameters);
-```
-
-```java
-Map parameters = new HashMap();
-parameters.put("ref", "taptap");
-TDSFriends.generateFriendInvitationLink("Taro", parameters, new Callback() {
- // Other things to do
-});
-```
-
-```objc
-NSError *error;
-TDSFriendLinkOption *option = [TDSFriendLinkOption new];
-option.roleName = @"Taro";
-option.queries = @{
- @"ref" : @"taptap",
-};
-NSString *inviteUrl = [TDSFriends generateFriendInvitationLinkWithOption:option error:&error];
-```
-
-
-
-### Handling Invitation Links
-
-After the player opens the game with an invitation link, you need to call the following interface.
-The SDK will automatically send a friend request to the corresponding player.
-
-
-
-```cs
-public class DeepLinkManager : MonoBehaviour
-{
- // Other logic
- private async void onDeepLinkActivated(string url) {
- await TDSFriends.HandleFriendInvitationLink(url);
- }
-}
-```
-
-```java
-public class FriendsActivity extends AppCompatActivity {
-// Other logic
- public void onHandleLink(View view) {
- TDSFriends.handFriendInvitationLink(url, new Callback() {
- @Override
- public void onSuccess(Void result) {
- // Other things to do
- }
-
- @Override
- public void onFail(TDSFriendError error) {
- // Other things to do
- }
- });
- }
-}
-```
-
-```objc
-- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
- return [TDSFriends handleFriendInvitationLink:url
- callback:^(BOOL succeeded, TDSFriendsLinkInfo * _Nullable linkInfo, NSError * _Nullable error) {
- if (error) {
- // handle error
- }
- }];
-}
-```
-
-
-
-You can also parse the link with the following interface provided by the SDK and get the player’s `objectId` and name as well as other parameters. You can perform your custom logic with them.
-
-
-
-```cs
-public class DeepLinkManager : MonoBehaviour
-{
- // Other logic
- private async void onDeepLinkActivated(string url) {
- TDSFriendLinkInfo invitation = TDSFriends.ParseFriendInvitationLink(url);
- string userObjectId = invitation.Identity;
- string name = invitation.RoleName;
- Dictionary parameters = invitation.Queries;
- await TDSFriends.Follow(userObjectId);
- }
-}
-```
-
-```java
-TDSFriendLinkInfo linkInfo = TDSFriends.parseFriendInvitationLink("url");
-String userObjectId = linkInfo.getIdentity();
-String name = linkInfo.getRoleName();
-Map parameters = linkInfo.getQueries();
-```
-
-```objc
-TDSFriendLinkInfo *linkInfo = [TDSFriends parseFriendInvitationLink:(NSURL *)url];
-NSString *userObjectId = linkInfo.identity;
-NSString *name = linkInfo.roleName;
-NSDictionary *parameters = linkInfo.queries;
-```
-
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/third-party.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/third-party.mdx
deleted file mode 100644
index f5c4a8066..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/friends/third-party.mdx
+++ /dev/null
@@ -1,154 +0,0 @@
----
-title: Friends on Third-Party Platforms
-sidebar_label: Friends on Third-Party Platforms
-sidebar_position: 5
-slug: /sdk/friends/third-party
----
-
-
-
-
-
-import { Conditional } from "/src/docComponents/conditional";
-import MultiLang from "/src/docComponents/MultiLang";
-
-Before continuing, please familiarize yourself with [the general interfaces of the Friends module](/sdk/friends/guide/)
-
-## Retrieving Friends on Third-Party Platforms
-
-The following interface can be used to retrieve the current player’s friends (mutual followers) on third-party platforms (like TapTap) who have played the same game.
-Besides the friend list, the interface also returns a cursor.
-You can implement pagination by providing the cursor and a limit when performing the query.
-The players in the result can be sorted by online status (those who are online will be placed at the beginning).
-
-The returned list of friends on the third-party platform will include each player’s ID, nickname, and profile picture on the platform.
-As mentioned above, only those who have played the same game (i.e., logged in to the game with their third-party account) will be included in the friend list.
-In general, those friends have logged in to the game with their third-party accounts that are based on the built-in account system, so the `TDSFriendInfo` of each player will be included in the friend list as well.
-
-In special cases, if a friend is logged in with their third-party account without using the built-in account system, the friend will still be included in the friend list, but their `TDSFriendInfo` will be `null`.
-For example, assuming a game has two variations of APKs, variation A logs players in with F using the built-in account system and supports the Friends service while variation B also logs players in with F but without using the built-in account system and doesn’t support the Friends service. Player D and Player E are friends on F and they each use a different variation of the game (Player D uses the variation A) but both log in with F. Now if Player D retrieves their friends on F with the following interface, the returned list will include Player E, but the `TDSFriendInfo` of Player E will be `null`.
-
-
-
-```cs
-// First query
-string platform = "taptap";
-string cursor = null;
-// Defaults to 50; no larger than 500
-int limit = 50;
-// Sort by online status
-SortCondition sortCondition = SortCondition.OnlineCondition
-ThirdPartyFriendResult result = await TDSFriends.QueryThirdPartyFriendList(platform, cursor, limit, condition: sortCondition);
-
-ReadOnlyCollection friends = result.FriendList;
-foreach (ThirdPartyFriend friend in friends) {
- string thirdPartyId = friend.Id;
- string thirdPartyNickName = friend.Name;
- string thirdPartyAvatarUrl = friend.Avatar;
- TDSFriendInfo info = friend.FriendInfo;
-}
-
-// Pagination
-string cursor = result.Cursor;
-ThirdPartyFriendResult more = await TDSFriends.QueryThirdPartyFriendList(platform, cursor, limit, condition: sortCondition);
-```
-
-```java
-ThirdPartyFriendRequestConfig config = new ThirdPartyFriendRequestConfig.Builder()
- .platform(ThirdPartyFriendRequestConfig.PLATFORM_TAPTAP)
- .pageSize(50) /* Defaults to 50; no larger than 500 */
- .sortCondition(SortCondition.getOnlineCondition()) /* Sort by online status */
- .build();
-// First query
-TDSFollows.queryThirdPartyMutualList(config, null, new Callback() {
- @Override
- public void onSuccess(ThirdPartyFriendResult result) {
- List friends = result.getFriendList();
- for (ThirdPartyFriend friend : friends) {
- String thirdPartyId = friend.getUserId();
- String thirdPartyNickName = friend.getUserName();
- String thirdPartyAvatarUrl = friend.getUserAvatar();
- TDSFriendInfo info = friend.getTdsFriendInfo();
- }
-
- // Pagination
- String cursor = result.getCursor();
- TDSFollows.queryThirdPartyMutualList(config, cursor, new Callback() {
- /* Other things to do */
- }
- }
- @Override
- public void onFail(TDSFriendError error) {
- toast("query error = " + error.code + " msg = " + error.detailMessage);
- }
-});
-```
-
-```objc
-TDSThirdPartyFriendQueryOption *option = [TDSThirdPartyFriendQueryOption new];
-option.platform = TDSThirdPartyFriendPlatformTaptap;
-option.limit = 50;// Defaults to 50; no larger than 500
-__block NSString *cursor; // Cursor
-
-[TDSFriends queryThirdPartyFriendListWithOption:option
-callback:^(TDSThirdPartyFriendResult * _Nullable result, NSError * _Nullable error) {
- for (TDSThirdPartyFriend* friend in result.friendList) {
- NSString *thirdPartyId = friend.userId;
- NSString *thirdPartyNickName = friend.userName;
- NSString *thirdPartyAvatarUrl = friend.userAvatar;
- TDSFriendInfo *info = friend.tdsFriendInfo;
- }
- cursor = result.cursor;
-}];
-
-// Pagination
-option.from = cursor;
-[TDSThirdPartyFriend queryThirdPartyFriendListWithOption:option
-callback:^(TDSThirdPartyFriendResult * _Nullable result, NSError * _Nullable error) {
- // Other things to do
-}];
-```
-
-
-
-By default, the SDK will fetch the result from the local cache.
-To always fetch the result from the cloud, you can specify the caching strategy when performing the query.
-The SDK will always cache the result regardless of the caching strategy you specify.
-In other words, the caching strategy only affects whether the cache will be read. It won’t affect whether the result will be cached.
-
-
-
-```cs
-ThirdPartyFriendResult result = await TDSFriends.QueryThirdPartyFriendList(platform, cursor, limit,
- TDSFriends.ThirdPartyFriendRequestCachePolicy.OnlyNetwork, sortCondition);
-```
-
-```java
-ThirdPartyFriendRequestConfig config = new ThirdPartyFriendRequestConfig.Builder()
- .platform(ThirdPartyFriendRequestConfig.PLATFORM_TAPTAP)
- .sortCondition(SortCondition.getOnlineCondition())
- .cachePolicy(ThirdPartyFriendRequestConfig.CachePolicy.ONLY_NETWORK)
- .pageSize(50)
- .build();
-```
-
-```objc
-option.cachePolicy = TDSThirdPartyFriendCachePolicyOnlyNetwork;
-```
-
-
-
-At this moment, the following third-party platforms are supported:
-
-
-
-- `taptap` (Please submit a ticket to us to enable it)
-
-
-
-
-
-- `taptap` (Please submit a ticket to us to enable it)
-- `facebook` (The game needs to support Facebook Login)
-
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/_category_.json
deleted file mode 100644
index 31d5ca7eb..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "推送通知",
- "collapsed": true,
- "position": 19
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/features.mdx
deleted file mode 100644
index 3ea03273d..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/features.mdx
+++ /dev/null
@@ -1,46 +0,0 @@
----
-title: Push Notification Introduction
-sidebar_label: Introduction
-sidebar_position: 1
-slug: /sdk/push/features/
----
-
-
-
-
-
-import {Conditional} from '/src/docComponents/conditional';
-
-The Push Notification service allows you to send push notifications to your app’s users, whether they use iOS or Android.
-
-You can send push notifications using the iOS or Android SDK, or via our REST API.
-
-## Features
-
-You can easily integrate the Push Notification service into your app and start sending push notifications to both iOS and Android users.
-
-### A Variety of Message Types
-
-You can send push notifications that include text messages, rich media messages, and custom messages. You can also send pass-through messages.
-
-### Statistics
-
-You can view statistics such as arrival rates and open rates on the dashboard.
-
-### Schedule Messages and Push by Criteria
-
-We provide an easy-to-use dashboard that allows non-technical users to easily schedule push notifications or send push notifications to users based on specific criteria.
-
-## Our Benefits
-
-- Our service is based on WebSocket TLS, ensuring high delivery rates without compromising security.
-
-- We have implemented our own binary protocol together with minimalist commands that minimize power and data consumption on the clients.
-
-- Our service encapsulates the push notification services provided by major Chinese handset vendorsFCM, providing you with reliable and unified Android push notification solutions.
-
-
-
-- We provide dedicated and reliable connections to APNs clusters, ensuring high delivery rates for the push notifications you send out.
-
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/Unreal.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/Unreal.mdx
deleted file mode 100644
index 258380c8b..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/Unreal.mdx
+++ /dev/null
@@ -1,155 +0,0 @@
----
-title: Unreal Push Guide
-sidebar_label: Unreal Push
-sidebar_position: 6
-slug: /sdk/push/guide/Unreal/
----
-
-
-
-
-
-import {Conditional} from '/src/docComponents/conditional';
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-
-
-This article will show you how to use push notifications in your Unreal project. We recommend that you take a look at [Push Notification Overview](/sdk/push/guide/overview/) if you haven’t already.
-
-Currently we only support iOS and some Android vendors (Huawei, Xiaomi, vivo, OPPO, Meizu, Honor)FCM.
-
-## Getting Started
-
-### Prerequisites
-
-* Install **UE 4.26** or higher on your computer
-* iOS **12** or higher
-* Android MinSDK is **API21** or higher
-
-### iOS
-
-Please first obtain an iOS push notification certificate by following [APNs Configuration Guide](/sdk/push/guide/ios-cert/).
-
-### Android
-
-Please first apply for push notification permissions from Android vendorsFCM by following [Android Mixpush Guide](/sdk/push/guide/android-mixpush/).
-
-Note that you only need to follow the guide to apply for push notification permissions from Android vendorsFCM. You **don’t** need to follow the guide to set up push notifications for Android.
-
-## Integrate the Push Notification Service
-
-### Install the Plugin
-
-* Download **[TapSDK.zip](https://github.com/taptap/TapSDK-UE4/releases)**, unzip it, and copy `LeanCloudPush` and `LeanCloud` to the `Plugins` directory of the project. If AndroidX configurations are missing from the project, you can also copy `AndroidX` to the project.
-* Restart Unreal Editor.
-* Go to **Edit > Plugins > Project > TapTap** and enable the `LeanCloudPush` module.
-
-### Add Dependencies
-
-Add the following dependencies to **Project.Build.cs**:
-
-```c#
-PublicDependencyModuleNames.AddRange(new string[] {
- "Core",
- "CoreUObject",
- "Engine",
- "Slate",
- "SlateCore",
- "Http",
- "Json",
- "JsonUtilities",
-});
-
-if (Target.Platform == UnrealTargetPlatform.IOS || Target.Platform == UnrealTargetPlatform.Android)
-{
- PublicDependencyModuleNames.AddRange(
- new string[]
- {
- // 推送接入
- "LeanCloudPush",
- "LeanCloudMobile"
- }
- );
-}
-else
-{
- PublicDependencyModuleNames.AddRange(
- new string[]
- {
- "LeanCloud"
- }
- );
-}
-```
-
-### Project Configurations
-
-#### iOS
-
-Add the following configurations to **DefaultEngine.ini**:
-
-```ini
-[/Script/IOSRuntimeSettings.IOSRuntimeSettings]
-bEnableRemoteNotificationsSupport=True
-```
-
-#### Android
-
-* Create a directory named **app** and copy the file named **agconnect-services.json** downloaded from Huawei Developer Center into this directory
-* Add the following code to the Android UPL file of the project (create the file if it doesn’t exist) and fill in the configurations
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
- ```
-
-### Import Header Files
-
-```cpp
-#if PLATFORM_IOS
-#include "iOS/LCIOSPush.h"
-#elif PLATFORM_ANDROID
-#include "Android/LCAndroidPush.h"
-#endif
-```
-
-### Usage
-
-Enter the configurations obtained from developer platforms into the following interface:
-
-```cpp
-#if PLATFORM_IOS
- FLCIOSPush::Register("iOS Team ID");
-#elif PLATFORM_ANDROID
- FString DeviceName = FLCAndroidPush::GetDeviceName().ToLower();
- if (DeviceName.Contains("huawei")) {
- FLCAndroidPush::RegisterHuaWei();
- } else if (DeviceName.Contains("oppo")) {
- FLCAndroidPush::RegisterOPPO("OPPO’s AppKey", "OPPO’s AppSecret");
- } else if (DeviceName.Contains("vivo")) {
- FLCAndroidPush::RegisterVIVO();
- } else if (DeviceName.Contains("meizu")) {
- FLCAndroidPush::RegisterMeiZu("Meizu’s AppId", "Meizu’s AppKey");
- } else if (DeviceName.Contains("honor")) {
- FLCAndroidPush::RegisterHonor();
- } else {
- FLCAndroidPush::RegisterXiaoMi("Xiaomi’s AppId", "Xiaomi’s AppKey");
- }
-#endif
-```
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/_category_.json
deleted file mode 100644
index 3446da847..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "开发指南",
- "collapsed": true,
- "position": 2
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/android.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/android.mdx
deleted file mode 100644
index 83ce91bf8..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/android.mdx
+++ /dev/null
@@ -1,395 +0,0 @@
----
-title: Android Push Guide
-sidebar_label: Android Push
-sidebar_position: 3
-slug: /sdk/push/guide/android/
----
-
-
-
-
-
-import CodeBlock from '@theme/CodeBlock';
-import sdkVersions from '/src/docComponents/sdkVersions';
-import {Conditional} from '/src/docComponents/conditional';
-
-Please read [Push Notification Overview](/sdk/push/guide/overview/) first to understand the concepts.
-
-There is a special demo for Android message push. See [Android-Push-Demo](https://github.com/leancloud/android-push-demo).
-
-## Introduction to the Push Process
-
-The Push Notification service on Android relies primarily on the PushService on the client. The PushService is an application-independent process that is created when the application is first launched and then lives (as much as possible) in the background, and is mainly responsible for maintaining a long WebSocket connection with the cloud push server.
-Thus, as long as PushService is alive, any message that needs to be pushed to the current device will be pushed immediately to the push server; if PushService is killed, the push channel will be interrupted and Android devices will not receive any push messages. After establishing a long WebSocket connection with the push server, PushService will also receive multiple unsuccessful push messages cached by the server at once.
-
-## Integrate the Push Notification Service
-
-To integrate the push service, you need the realtime-android library. First, open `build.gradle` in the `app` directory and configure it like this:
-
-
-{`dependencies {\n
-implementation 'cn.leancloud:realtime-android:${sdkVersions.leancloud.java}'
-implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'\n
-}`}
-
-
-Then create a new Java class called **MyLeanCloudApp** and make it inherit from the **Application** class with the following sample code:
-
-```java
-public class MyLeanCloudApp extends Application {
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- // Pass this, AppId, and AppKey as initialization parameters
- LeanCloud.initialize(this, "{{appid}}", "{{appkey}}", "https://please-replace-with-your-customized.domain.com");
- }
-}
-```
-
-### Configure AndroidManifest
-
-Make sure your `AndroidManifest.xml` contains the following in ``:
-
-```xml
-
-```
-
-Please also set the necessary permissions:
-
-```xml
-
-
-```
-
-In order for the application to receive pushes even when it is closed, you need to add to ``:
-
-```xml
-
-
-
-
-
-
-
-```
-
-#### Push Wakeup
-
-If you want to support inter-app push wakeup mechanism, i.e. two apps using cloud push on the same device, after app A is killed, when app B is woken up it can wake up app A's push at the same time, you can configure it like this:
-
-```xml
-
-```
-
-#### Complete `AndroidManifest.xml`
-
-The complete `AndroidManifest.xml` file configuration is shown below:
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-
-### Save Installation
-
-When the application is installed on the user's device, the SDK automatically generates an Installation object if the push feature is to be used. This object is essentially the installation information generated by the application on the device and must first be stored on the cloud in order for the device to receive push notifications:
-
-```java
-LCInstallation.getCurrentInstallation().saveInBackground();
-```
-
-**This code should be called once at application startup** to ensure that the device is registered with the cloud. You can listen to the callback to get the installationId to do the data association.
-
-```java
-LCInstallation.getCurrentInstallation().saveInBackground().subscribe(new Observer() {
- @Override
- public void onSubscribe(Disposable d) {
- }
- @Override
- public void onNext(LCObject avObject) {
- // Operations such as associating the installationId with the user table.
- String installationId = LCInstallation.getCurrentInstallation().getInstallationId();
- System.out.println("Successfully saved: " + installationId );
- }
- @Override
- public void onError(Throwable e) {
- System.out.println("Could not save. Error message: " + e.getMessage());
- }
- @Override
- public void onComplete() {
- }
-});
-```
-
-### Enable the Push Notification Service
-
-Start the push service by calling the following code and also set the default Activity to open.
-
-```java
-// Set the default Activity to open
-PushService.setDefaultPushCallback(this, PushDemo.class);
-```
-
-### Subscribe to Channels
-
-Your application can subscribe to a channel by calling the `PushService.subscribe` method before saving your Installation:
-
-```java
-// Subscribe to the channel and open the corresponding Activity when the channel message arrives
-// Parameters in order: current context, channel name, class of the callback object
-PushService.subscribe(this, "public", PushDemo.class);
-PushService.subscribe(this, "private", Callback1.class);
-PushService.subscribe(this, "protected", Callback2.class);
-```
-
-Note:
-
-- **The channel name can only contain upper and lower case English letters, numbers, underscores (`_`), hyphens (`-`), equal signs (`=`), and Chinese characters.**
-- The callback object refers to the Activity page that the user enters by clicking the notification in the notification bar.
-
-To unsubscribe from a channel:
-
-```java
-PushService.unsubscribe(context, "protected");
-//You must save the Installation again after unsubscribing
-LCInstallation.getCurrentInstallation().saveInBackground();
-```
-
-### Adapting for Android 8.0
-
-After calling `LeanCloud.initialize`, you need to call `PushService.setDefaultChannelId(context, channelid)` to set the default `channel` for notification display, otherwise the message will not be displayed. See Google's official documentation [Creating a notification](https://developer.android.com/training/notify-user/channels.html) for more information about channel ID.
-
-In addition, our push service also supports multiple push channels. On the client side, developers can create a new notification channel by calling `PushService` with the following method (or you can call the underlying API to create it yourself):
-
-```java
-public static void createNotificationChannel(Context context, String channelId, String channelName,
- String description, int importance,
- boolean enableLights, int lightColor,
- boolean enableVibration, long[] vibrationPattern)
-```
-
-Note the `channelId` here because we will need it later when we send push notifications. When sending a push request, the custom keyword `_notificationChannel` allows you to select a different channel for the message presentation.
-
-For example, the following request will be displayed on the client in the channel with notification ID "1":
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "where": {"key" : "value"}
- "data": {
- "alert": "Message content",
- "title": "Title to display in the notification bar",
- "_notificationChannel": "1"
- }
- }' \
- https://{{host}}/1.1/push
-```
-
-## Send Push Notifications
-
-
-By default, **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Settings** has **Prevent clients from sending push notifications** checked to prevent clients from pushing messages to any target device in the app without restriction.
-We recommend that developers check this box to send push messages via the REST API or the dashboard.
-If there is a need to send pushes from the client, you will need to uncheck this first.
-
-### Send to All Devices
-
-```java
-LCPush push = new LCPush();
-Map pushData = new HashMap();
-pushData.put("alert","push message to android device directly");
-push.setPushToAndroid(true);
-push.setData(pushData);
-push.sendInBackground().subscribe(new Observer() {
- @Override
- public void onSubscribe(Disposable d) {
- }
- @Override
- public void onNext(JSONObject jsonObject) {
- System.out.println("Push successful" + jsonObject);
- }
- @Override
- public void onError(Throwable e) {
- System.out.println("Push failed. Error message: " + e.getMessage());
- }
- @Override
- public void onComplete() {
- }
-});
-```
-
-### Send to Specific Users
-
-Send to users on the "public" channel:
-
-```java
-LCQuery pushQuery = LCInstallation.getQuery();
-pushQuery.whereEqualTo("channels", "public");
-LCPush push = new LCPush();
-push.setQuery(pushQuery);
-push.setMessage("Push to channel.");
-push.setPushToAndroid(true);
-push.sendInBackground().subscribe(new Observer() {
- @Override
- public void onSubscribe(Disposable d) {
- }
- @Override
- public void onNext(JSONObject jsonObject) {
- System.out.println("Push successful" + jsonObject);
- }
- @Override
- public void onError(Throwable e) {
- System.out.println("Push failed. Error message: " + e.getMessage());
- }
- @Override
- public void onComplete() {
- }
-});
-```
-
-To send to a user with a specific Installation id, you would typically associate LCInstallation with the device's logged-in user LCUser as a property, and then you can send a message to a specific user by querying the InstallationId with the following code to achieve a private message-like function:
-
-```java
-LCQuery pushQuery = LCInstallation.getQuery();
-// Assuming that THE_INSTALLATION_ID is the installationId stored in the user table,
-// you can retrieve it and store it in the user table when the application is opened by the user
-pushQuery.whereEqualTo("installationId", THE_INSTALLATION_ID);
-LCPush.sendMessageInBackground("Tarara invited you to play Arc Symphony with her!",pushQuery).subscribe(new Observer() {
- @Override
- public void onSubscribe(Disposable d) {
- }
- @Override
- public void onNext(Object object) {
- System.out.println("Push successful" + object);
- }
- @Override
- public void onError(Throwable e) {
- System.out.println("Push failed. Error message: " + e.getMessage());
- }
- @Override
- public void onComplete() {
- }
-});
-```
-
-## Read More: How to Reply to Push Messages
-
-### Message Format
-
-For the specific message format, see the [Push Notification REST API](/sdk/push/guide/rest/).
-For Android devices, the default message content parameters support the following attributes:
-
-```json
-{
- "alert": "Message content",
- "title": "Title to display in the notification bar",
- "custom-key": "A custom property added by the user; custom-key is just an example, feel free to replace it",
- "silent": false, // Used to control whether to turn off the notification bar alert; default is false, i.e. do not turn off the notification bar alert
- "action": "com.your_company.push" // Must be provided if using a custom receiver
-}
-```
-The silent property above is a flag for the push-through message and the notification bar message. If silent is true, the message is not displayed in the notification bar; if silent is false, the message is displayed in the notification bar.
-
-As mentioned earlier, after PushService receives a push message, it determines whether the message is expired and whether a duplicate message has been received before forwarding the message, and only non-expired and non-duplicate messages are notified to the application by sending a local notification or broadcast.
-
-### How Notification Bar Messages Respond to User Click Events
-
-When the PushService sends a notification bar message, it sets the response class for the notification bar, depending on whether the developer calls `PushService.setDefaultPushCallback(context, clazz)` or `PushService.subscribe(context, "channel", clazz)` to set the callback class that sets the response class for the notification bar.
-
-In the onCreate function of the callback class, the developer can then use the following code to get the specific data of the push message:
-
-```java
-public class CallbackActivity extends Activity {
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.callback2);
-
- // Get the push message data
- String message = this.getIntent().getStringExtra("com.avoscloud.Data");
- String channel = this.getIntent().getStringExtra("com.avoscloud.Channel");
- System.out.println("message=" + message + ", channel=" + channel);
- }
-}
-```
-
-### Customized Receiver
-
-If you want to push messages that don't appear in the Android notification bar, but instead execute the application's predefined logic, you need to declare your own Receiver in the `AndroidManifest.xml` of your Android project:
-
-```xml
-
-
-
-
-
-
-
-
-```
-
-Here `com.avos.avoscloud.PushDemo.MyCustomReceiver` is your Android's Receiver class, and `` must correspond to the `action` specified in the push's data.
-
-Your Receiver can be implemented like this:
-
-```java
-public class MyCustomReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- // Retrieve the push message data
- String message = intent.getStringExtra("com.avoscloud.Data");
- String channel = intent.getStringExtra("com.avoscloud.Channel");
- System.out.println("message=" + message + ", channel=" + channel);
- }
-}
-```
-
-Also, the request to send the push is changed accordingly, e.g:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "channels":[ "public"],
- "data": {
- "action": "com.avos.UPDATE_STATUS",
- "name": "LeanCloud."
- }
- }' \
- https://{{host}}/1.1/push
-```
-
-Note that if you are using a custom Receiver, the message sent must have an action and its value exists in the `` list of the custom receiver configuration, for example `'com.avos.UPDATE_STATUS'`. Please use your own action and try not to confuse it with other applications. It is recommended to use the domain name to define it.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/ios-cert.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/ios-cert.mdx
deleted file mode 100644
index c8e02f509..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/ios-cert.mdx
+++ /dev/null
@@ -1,150 +0,0 @@
----
-title: APNs Configuration Guide
-sidebar_label: APNs Configuration
-sidebar_position: 1
-slug: /sdk/push/guide/ios-cert/
----
-
-
-
-
-
-APNs is Apple’s push notification service. It allows app developers to send push notifications to apps installed on Apple devices.
-
-This article explains how to add APNs support to apps installed on Apple devices and what configurations you need to set up on the Developer Center.
-
-## Enable Push Notifications
-
-Follow these two steps to enable push notifications for your app:
-
-1. Enable the push notifications permission for your project.
-2. Enable push notifications for the corresponding App ID on Apple Developer.
-
-### Enable the Push Notifications Permission
-
-To add the required permission to the app, enable push notifications for the app in Xcode.
-
-To do this, open the Xcode project, go to **Project > Target > Capabilities**, click the plus sign, select `Push Notifications`, and add it. The result should look like the screenshot below:
-
-![Project Add Capability](/img/apns_setup/project_add_capability.png)
-
-### Enable Push Notifications for the App ID
-
-To enable push notifications for an App ID, go to Apple Developers, go to **Certificates, Identifiers & Profiles**, click **Identifiers** on the sidebar, and click the corresponding App ID (which is the Bundle Identifier in the Xcode project). Now select the `Push Notifications` checkbox and click “Save” to save the change. The result should look like the screenshot below:
-
-![App ID Add Capability](/img/apns_setup/app_id_add_capability.png)
-
-## Select a Push Method
-
-Apple offers two ways to send push notifications, each with its own advantages and disadvantages. Both are supported by our Push Notification service, and you can choose one based on your needs.
-
-1. Token-Based Push Notification (Recommended).
- - Technically, this is faster than certificate-based push notification.
- - The same key can be used by multiple applications.
- - The same key can be used to send push notifications to multiple applications under an Apple Developer account.
- - The same key can be used to send push notifications to test apps and production apps.
- - A key never expires, so you don’t have to regenerate it like you do with certificates.
-2. Certificate-Based Push Notification.
- - A certificate is associated with an Apple Developer App ID and can only be used to send push notifications to the associated app.
- - APNs comes with a development environment and a production environment. You may need to set up different certificates for apps in different environments.
- - A certificate has an expiration date, and you will need to regenerate and reconfigure certificates periodically.
-
-In general, token-based push notification is easier to set up and provides better usability and functionality than certificate-based push notification. We recommend that you use token-based push notification.
-
-> **Note that** you cannot use both methods simultaneously, and token-based push notification takes precedence over certificate-based push notification. Once you set up token-based push notifications, all push notifications sent from your app will use this method.
-
-### Token-Based Push Notification (Recommended)
-
-To enable token-based push notification, first generate and download an Auth Key from Apple Developer, then upload it to the Developer Center and set up the appropriate configurations. Once complete, you will be able to send push notifications from your app.
-
-#### Generate a Key
-
-To generate a key, sign in to Apple Developer, navigate to **Certificates, Identifiers & Profiles**, click **Keys** in the sidebar, and then click the plus sign (+). Enter a unique name for the key and select **Apple Push Notifications service (APNs)**:
-
-> **Note that** if you don’t see **Keys** in the sidebar, **your account may not have the necessary permissions**.
-
-![Generate Push Key](/img/apns_setup/generate_push_key.png)
-
-Proceed to the next page and review the key details. If everything looks good, generate the key and download it. You will get a text file with the extension `.p8`.
-
-> **Be sure to** keep this file (with the extension `.p8`) in a safe place. You won’t be able to download it again because the key is not stored in your Apple Developer account. If the download button is disabled, it means you have already downloaded the key.
-
-#### Configure the Key
-
-After downloading the key (as a `.p8` file), you will need to upload it to the Developer Center and complete some configurations:
-
-1. Go to **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Settings > iOS Token Authentications** and click on **New Token Authentication**.
-2. Enter `Team ID`, `Key ID`, and `Topics` in the pop-up window and upload the key file (the `.p8` file) here.
- - `Team ID` is the ID of the team to which the Apple Developer account belongs. You can find it under **Membership** in Apple Developer.
- - `Key ID` is the ID of the key (the `.p8` file), which can be found by going to **Certificates, Identifiers & Profiles > Keys** in Apple Developer and clicking on the corresponding key.
- - `Topics` is the IDs of your apps (the Bundle Identifiers in Xcode projects). You can specify multiple topics by separating them with **commas**, as long as **they belong to the same Team ID**.
-3. Click **Add** to finish uploading and setting up the key.
-
-Once you have completed the above steps, you can send a test notification by going to **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Send notifications**.
-
-### Certificate-Based Push Notification
-
-To enable certificate-based push notification, a certificate must be generated for each app on Apple Developer. Each app can have **sandbox certificates** and **sandbox & production certificates**. Once you have obtained the certificates from Apple Developer, upload them to the Developer Center and you will be able to send push notifications using the certificates.
-
-#### Generate Certificates
-
-To generate certificates, sign in to Apple Developer, navigate to **Certificates, Identifiers & Profiles**, click **Certificates** in the sidebar, and then click the plus sign (+). Follow the steps below:
-
-1. Select a certificate type. The most common is `Apple Push Notification service SSL`. For this certificate type, you can choose between `Sandbox` and `Sandbox & Production`. `Sandbox` certificates can only be used in development environments, while `Sandbox & Production` can be used in both development and production environments. You can refer to the descriptions under each option for more information, as shown in the screenshot below:
-
- ![Select Cert Type](/img/apns_setup/select_cert_type.png)
-
-2. After selecting a certificate type, proceed to the next step and select an App ID (the Bundle Identifier in the Xcode project), then proceed to the next step. You will be prompted to upload a CSR file.
-3. Generate a CSR (Certificate Signing Request) file on your Mac by following these steps:
-
- - Open `Keychain Access` in `/Applications/Utilities`.
- - Select **Keychain Access > Certificate Assistant > Request a Certificate From a Certificate Authority…**.
- - In the Certificate Assistant window, enter an email address in `User Email Address` and a name for the key in `Common Name` (e.g., Gita Kumar Dev Key).
- - Leave `CA Email Address` blank.
- - Select `Saved to disk` and continue.
-
- ![Generate CSR](/img/apns_setup/generate_csr.png)
-
-4. Upload your CSR file (the `.certSigningRequest` file you saved from the last step) and continue. Download the generated certificate.
-
-#### Set up Certificates
-
-After you generate a certificate, you’ll need to upload the downloaded certificate and your private key to the Developer Center. Follow the instructions below:
-
-1. On the Mac you used to generate the CSR, double-click the downloaded certificate. macOS will import the certificate into `Keychain` and group it with the key of the CSR you generated earlier, as shown in the screenshot below:
-
- ![Cert With Key](/img/apns_setup/cert_with_key.png)
-
-2. Go to **Keychain Access > login > My Certificates** and **right-click** the imported certificate (not the key), select **Export**, and save the certificate to disk as a `.p12` file. A popup will appear asking for a password. **Leave the two fields blank so that you don’t give the file a password**. Now click OK. You may see another popup asking for the macOS login password. Enter the password and click Allow.
-3. Go to **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Settings > iOS push notification certificates** and upload the appropriate certificate (the `.p12` file exported in the last step).
- - If the certificate type is `Sandbox`, the certificate can only be uploaded to the development environment; if it is `Sandbox & Production`, the certificate can be uploaded to either the development or production environment.
-
-Once you have completed the above steps, you can send a test notification by going to **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Send notifications**.
-
-#### Fail to Upload a Certificate
-
-If you are unable to upload a certificate, it is usually because there is a problem with the certificate. Here are some common reasons:
-
-1. The certificate is not a push notification certificate. You can see this from the Common Name of the certificate, which can be viewed by double-clicking the certificate in `Keychain Access`. The Common Name of a push notification certificate contains either `Push Service` or `Pass Type ID`, as shown in the screenshot below:
-
- ![Cert Common Name](/img/apns_setup/cert_common_name.png)
-
- The Developer Center checks to see if the Common Name of the certificate contains any of the following prefixes:
-
- - `Apple Push Services`
- - `Apple Sandbox Push Services`
- - `Apple Development IOS Push Services`
- - `Apple Production IOS Push Services`
- - `Pass Type ID`
-
- > Apple may change the Common Name prefix of push notification certificates in the future. When this happens, we will update the list of prefixes we use to verify certificates.
-
-2. The certificate is being exported in the wrong format. Currently, the Developer Center only accepts certificates in `.p12` format. Please be sure to select this format when exporting the certificate.
-
-#### Certificate Expired
-
-If you try to send a push notification with an expired certificate, you will receive the error `The iOS certificate file is expired or disabled.`.
-
-Each time you submit a request to send push notifications, our server checks whether the certificate of the environment specified by the `prod` parameter has expired (if `prod` is not specified, the certificate of the production environment is checked). If the certificate is expired and the queried target devices may include iOS devices, the push notification request will be rejected.
-
-One solution is to replace the expired certificate with a new one. Another solution is to use the `deviceType` field in the query to specify that you want to send push notifications to non-iOS devices. See *Push Notification REST API Guide* for more information.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/ios.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/ios.mdx
deleted file mode 100644
index 78042ca99..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/ios.mdx
+++ /dev/null
@@ -1,1092 +0,0 @@
----
-title: iOS Push Guide
-sidebar_label: iOS Push
-sidebar_position: 2
-slug: /sdk/push/guide/ios/
----
-
-
-
-
-
-import MultiLang from '/src/docComponents/MultiLang';
-import Mermaid from "/src/docComponents/Mermaid";
-
-
-This article will show you how to use push notifications in your iOS project. We recommend that you take a look at [Push Notification Overview](/sdk/push/guide/overview/) if you haven’t already.
-
-## Configure APNs Certificates
-
-To use push notifications in your iOS project, you must first configure APNs certificates. See [APNs Configuration Guide](/sdk/push/guide/ios-cert/) for more information.
-
-## iOS Workflow Overview
-
-The first step is to register with APNs and obtain a token, then store the token in the cloud:
-
->APNs: 1. Call the official API and get the deviceToken
-APNs-->>iOS SDK: 2. Provide the deviceToken
-iOS SDK-->>Cloud: 3. The SDK stores the deviceToken in the _Installation table
-`}
-/>
-
-Then you can invoke the interface provided by the Push Notification service to send a push notification:
-
->Cloud: 1. Send an HTTPS request to the Push Notification API
-Cloud-->>APNs: 2. Find the appropriate deviceToken from _Installation and call the APNs API to send the push notification
-APNs->>iOS device: 3. Send the push notification
-`}
-/>
-
-## Installation
-
-Installation is a subclass of LCObject. You can use an Installation object to store the token and other data needed to send a push notification.
-
-The SDK comes with a default Installation object and **will cache the data after you save the default object to the cloud**. In general, the default object is used to store the device token. The code below shows how to get the default object:
-
-
-
-```objc
-LCInstallation *installation = [LCInstallation defaultInstallation];
-```
-
-```swift
-let installation = LCApplication.default.currentInstallation
-```
-
-
-
-In addition to using the default Installation object, you can also construct new Installation objects and store other tokens of special types (such as VoIP) in them. The following code constructs a new Installation object:
-
-
-
-```objc
-LCInstallation *installation = [[LCInstallation alloc] init];
-```
-```swift
-let installation = LCInstallation()
-```
-
-
-
-**The Instant Messaging service uses the device token obtained from the default Installation object. To use the push notification feature of the Instant Messaging service, make sure that the default Installation object has successfully saved the device token.**
-
-By default, an Installation object contains the following fields:
-
-Field|Type|Description
----|---|---
-deviceToken|String|The token used to send push notifications
-apnsTeamId|String|The Team ID used to send push notifications
-badge|Number|The number displayed in the app’s badge; primarily used to clear the badge
-channels|Array|An array of subscribed channels
-deviceProfile|String|Custom certificate’s name; primarily used to send push notifications with multiple certificates
-deviceType|String|The device type; the SDK automatically sets the value of this field; please avoid editing it manually
-apnsTopic|String|The app’s Bundle Identifier; the SDK automatically sets the value of this field; please avoid editing it manually
-timeZone|String|The device’s timezone; the SDK automatically sets the value of this field; please avoid editing it manually
-
-### Register With APNs and Obtain a Token
-
-Before saving the Installation, you must first register with APNs to obtain the token needed to send push notifications. The code below uses User Notification as an example:
-
-
-
-```objc
-#import
-#import
-
-- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
-
- // Be sure to initialize the app first
- [LCApplication setApplicationId:{{appid}}
- clientKey:{{appkey}}
- serverURLString:"https://please-replace-with-your-customized.domain.com"];
-
- [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
- switch ([settings authorizationStatus]) {
- case UNAuthorizationStatusAuthorized:
- dispatch_async(dispatch_get_main_queue(), ^{
- [[UIApplication sharedApplication] registerForRemoteNotifications];
- });
- break;
- case UNAuthorizationStatusNotDetermined:
- [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
- if (granted) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [[UIApplication sharedApplication] registerForRemoteNotifications];
- });
- }
- }];
- break;
- default:
- break;
- }
- }];
-
- return YES;
-}
-```
-```swift
-import LeanCloud
-import UserNotifications
-
-func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
-
- // Be sure to initialize the app first
- do {
- try LCApplication.default.set(
- id: {{appid}},
- key: {{appkey}},
- // Please replace xxx.example.com with the custom API domain you have added to your app
- serverURL: "https://xxx.example.com")
- } catch {
- print(error)
- return false
- }
-
- UNUserNotificationCenter.current().getNotificationSettings { (settings) in
- switch settings.authorizationStatus {
- case .authorized:
- DispatchQueue.main.async {
- UIApplication.shared.registerForRemoteNotifications()
- }
- case .notDetermined:
- UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
- if granted {
- DispatchQueue.main.async {
- UIApplication.shared.registerForRemoteNotifications()
- }
- }
- }
- default:
- break
- }
- }
-
- return true
-}
-```
-
-
-
-### Save the Token
-
-Once you have successfully registered with APNs, the system returns the deviceToken with the `didRegisterForRemoteNotificationsWithDeviceToken` function. In most cases, you can save the deviceToken and apnsTeamId in this function, as shown in the code below:
-
-
-
-```objc
-- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
-
- [[LCInstallation defaultInstallation] setDeviceTokenFromData:deviceToken
- teamId:@"YOUR_APNS_TEAM_ID"];
- [[LCInstallation defaultInstallation] saveInBackgroundWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- // save succeeded
- } else if (error) {
- NSLog(@"%@", error);
- }
- }];
-}
-```
-```swift
-func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
-
- LCApplication.default.currentInstallation.set(
- deviceToken: deviceToken,
- apnsTeamId: "YOUR_APNS_TEAM_ID")
- LCApplication.default.currentInstallation.save { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- }
-}
-```
-
-
-
-The device token changes when a user erases their device, restores the app from backup, or installs the app on a new device. To solve this problem, [Apple recommends][apple-apns] that the app requests the device token of the APNs when opening, and then sets and saves the token.
-Also, our system keeps track of the update times (`updatedAt`) of all Installation objects and removes the objects that haven’t been updated in a while.
-Therefore, we recommend that you build your app according to Apple’s recommended way to avoid Installation objects from being removed accidentally and to avoid push notification failure due to expired device tokens.
-
-[apple-apns]: https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns
-
-## Use Multiple Certificates
-
-Some developers may publish different apps that share the same data and messages (for example, a carpooling service may provide different apps for drivers and riders). If that’s your case, you can upload multiple custom certificates and configure different `deviceProfile`s for different devices so you can send push notifications to different apps with different certificates.
-
-When you upload a custom certificate, you are prompted for a certificate type, which is also the name of the deviceProfile. If the deviceProfile is set for the Installation, our system will ignore the certificates configured for the development and production environments and use the deviceProfile instead.
-
-## Special Push Types
-
-For special push notifications such as VoIP, because they don’t use the app’s Bundle Identifier as apnsTopic, you must change apnsTopic to the specified value before saving the token.
-
-## Send Push Notifications
-
-You can send iOS push notifications through the REST API or the dashboard.
-
-### Environments
-
-An iOS app comes with a **development** environment and a **production** environment.
-Apps installed using Xcode are in development environments. Apps published through the App Store, Ad-Hoc, and TestFlight are in production environments.
-
-When sending push notifications using the REST API or the dashboard, you can specify which environment to send push notifications to by specifying the `prod` parameter.
-When sending push notifications using `Push` with the SDK, push notifications are sent to the production environment by default.
-To send to the development environment instead, use the following method:
-
-
-
-```objc
-[LCPush setProductionMode:false];
-```
-```swift
-do {
- let environment: LCApplication.Environment = [.pushDevelopment]
- let configuration = LCApplication.Configuration(environment: environment)
- try LCApplication.default.set(
- id: {{appid}},
- key: {{appkey}},
- // Please replace xxx.example.com with the custom API domain you have added to your app
- serverURL: "https://xxx.example.com",
- configuration: configuration)
-} catch {
- print(error)
-}
-```
-
-
-
-Note that if you’re using the SDK, you can only specify which environment to send push notifications to; you can’t change the environment that the app itself belongs to.
-The environment the app belongs to can only be determined by how the app is distributed.
-
-Note that to prevent performance issues caused by massive certificate errors, when using the **development certificate**, you can send push notifications to a maximum of 20,000 devices at a time. If you exceed this limit, the system will reject the push notification request (you will see an error on **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Push records**). Please set the conditions carefully when using the development certificate.
-
-## Use Channels
-
-Channels allow you to implement a pub-sub model in your app. A device can subscribe to a channel, and when you send push notifications, you can specify which channels to send to.
-
-Note that a channel name can only contain uppercase and lowercase letters, numbers, underscores (`_`), hyphens (`-`), equal signs (`=`), and Chinese characters.
-
-### Subscribe and Unsubscribe
-
-To subscribe to the `Giants` channel:
-
-
-
-```objc
-LCInstallation *currentInstallation = [LCInstallation defaultInstallation];
-[currentInstallation addUniqueObject:@"Giants" forKey:@"channels"];
-[currentInstallation saveInBackground];
-```
-```swift
-do {
- try LCApplication.default.currentInstallation.append("channels", element: "Giants", unique: true)
- _ = LCApplication.default.currentInstallation.save({ (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-
-
-Be sure to save after subscribing.
-
-To unsubscribe:
-
-
-
-```objc
-LCInstallation *currentInstallation = [LCInstallation defaultInstallation];
-[currentInstallation removeObject:@"Giants" forKey:@"channels"];
-[currentInstallation saveInBackground];
-```
-```swift
-do {
- try LCApplication.default.currentInstallation.remove("channels", element: "Giants")
- _ = LCApplication.default.currentInstallation.save({ (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-
-
-Get all subscribed channels:
-
-
-
-```objc
-NSArray *subscribedChannels = [LCInstallation defaultInstallation].channels;
-```
-```swift
-let subscribedChannels: LCArray? = LCApplication.default.currentInstallation.channels
-```
-
-
-
-### Send to Channels
-
-To send to the “Giants” channel:
-
-
-
-```objc
-// Send a notification to all devices subscribed to the "Giants" channel.
-LCPush *push = [[LCPush alloc] init];
-[push setChannel:@"Giants"];
-[push setMessage:@"Giants is cool"];
-[push sendPushInBackground];
-```
-```swift
-let messageData: [String: Any] = [
- "alert": "Giants is cool"
-]
-
-let channels: [String] = ["Giants"]
-
-LCPush.send(data: messageData, channels: channels) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-
-
-To send to multiple channels, specify an array for `channels`:
-
-
-
-```objc
-NSArray *channels = [NSArray arrayWithObjects:@"Giants", @"Mets", nil];
-LCPush *push = [[LCPush alloc] init];
-
-// Be sure to use the plural 'setChannels'.
-[push setChannels:channels];
-[push setMessage:@"The Giants won against the Mets 2-3."];
-[push sendPushInBackground];
-```
-```swift
-let messageData: [String: Any] = [
- "alert": "The Giants won against the Mets 2-3."
-]
-
-let channels: [String] = ["Giants", "Mets"]
-
-LCPush.send(data: messageData, channels: channels) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-
-
-## Send to Specific Devices
-
-In most cases, using channels to specify targets to receive a push notification is sufficient. However, sometimes you may want to send push notifications to more specific devices. The Push Notification service allows you to query the Installation table using the LCQuery API and send push notifications to devices that match certain query conditions.
-
-Because Installation is a subclass of LCObject, you can store data of any type in an Installation object and associate the Installation with other data in your app. This gives you the ability to send customized and dynamic push notifications to your users.
-
-### Save Data Into Installation Objects
-
-To add three new fields to the Installation:
-
-
-
-```objc
-// Store app language and version
-LCInstallation *installation = [LCInstallation defaultInstallation];
-
-[installation setObject:@(YES) forKey:@"scores"];
-[installation setObject:@(YES) forKey:@"gameResults"];
-[installation setObject:@(YES) forKey:@"injuryReports"];
-[installation saveInBackground];
-```
-```swift
-do {
- let currentInstallation = LCApplication.default.currentInstallation
-
- try currentInstallation.set("scores", value: true)
- try currentInstallation.set("gameResults", value: true)
- try currentInstallation.set("injuryReports", value: true)
-
- _ = currentInstallation.save({ (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-
-
-You can set an owner for the Installation. This could be the currently logged in user:
-
-
-
-```objc
-// Saving the device's owner
-LCInstallation *installation = [LCInstallation defaultInstallation];
-[installation setObject:[LCUser currentUser] forKey:@"owner"];
-[installation saveInBackground];
-```
-```swift
-do {
- let currentInstallation = LCApplication.default.currentInstallation
-
- if let currentUser = LCApplication.default.currentUser {
- try currentInstallation.set("owner", value: currentUser)
- }
-
- _ = currentInstallation.save({ (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- })
-} catch {
- print(error)
-}
-```
-
-
-
-### Send Push Notifications Based on Query
-
-Once you start storing data with Installation, you can build a query to send push notifications to a subset of all devices registered with your app:
-
-
-
-```objc
-// Create our Installation query
-LCQuery *pushQuery = [LCInstallation query];
-[pushQuery whereKey:@"injuryReports" equalTo:@(YES)];
-
-// Send push notification to query
-LCPush *push = [[LCPush alloc] init];
-[push setQuery:pushQuery]; // Set our Installation query
-[push setMessage:@"Willie Hayes injured by own pop fly."];
-[push sendPushInBackground];
-```
-```swift
-let query = LCQuery(className: "_Installation")
-query.whereKey("injuryReports", .equalTo(true))
-
-let messageData: [String: Any] = [
- "alert": "Willie Hayes injured by own pop fly."
-]
-
-LCPush.send(data: messageData, query: query) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-
-
-You can also perform queries on channels:
-
-
-
-```objc
-// Create our Installation query
-LCQuery *pushQuery = [LCInstallation query];
-[pushQuery whereKey:@"channels" equalTo:@"Giants"]; // Set channel
-[pushQuery whereKey:@"scores" equalTo:@(YES)];
-
-// Send push notification to query
-LCPush *push = [[LCPush alloc] init];
-[push setQuery:pushQuery];
-[push setMessage:@"Giants scored against the A's! It's now 2-2."];
-[push sendPushInBackground];
-```
-```swift
-let query = LCQuery(className: "_Installation")
-query.whereKey("channels", .equalTo("Giants"))
-query.whereKey("scores", .equalTo(true))
-
-let messageData: [String: Any] = [
- "alert": "Giants scored against the A's! It's now 2-2."
-]
-
-LCPush.send(data: messageData, query: query) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-
-
-If you have stored relations with other objects using Installation, we can also use that data. For example, to send push notifications to devices near Peking University:
-
-
-
-```objc
-// Find users near a given location
-LCQuery *userQuery = [LCUser query];
-[userQuery whereKey:@"location"
- nearGeoPoint:beijingUniversityLocation,
- withinMiles:[NSNumber numberWithInt:1]]
-
-// Find devices associated with these users
-LCQuery *pushQuery = [LCInstallation query];
-[pushQuery whereKey:@"user" matchesQuery:userQuery];
-
-// Send push notification to query
-LCPush *push = [[LCPush alloc] init];
-[push setQuery:pushQuery]; // Set our Installation query
-[push setMessage:@"Free hotdogs at the Tarara concession stand!"];
-[push sendPushInBackground];
-```
-```swift
-let beijingUniversityLocation = LCGeoPoint(latitude: 39.9869, longitude: 116.3059)
-
-let userQuery = LCQuery(className: "_User")
-userQuery.whereKey("location", .locatedNear(beijingUniversityLocation, minimal: nil, maximal: nil))
-
-let pushQuery = LCQuery(className: "_Installation")
-pushQuery.whereKey("user", .matchedQuery(userQuery))
-
-let messageData: [String: Any] = [
- "alert": "Free hotdogs at the Tarara concession stand!"
-]
-
-LCPush.send(data: messageData, query: pushQuery) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-
-
-## Options
-
-In addition to sending a text message, you can play a sound, set the number on the badge, or include other custom data in your push notifications. You can also set an expiration time for your push notifications.
-
-### Customize Notifications
-
-If you want to send something in addition to a text message, you can create your own notification data. Note that there are some reserved fields that have special meanings:
-
-Reserved field|Description
----|---
-`alert`|The text content of the push notification.
-`badge`|The number on the badge above the app icon. You can set it to a specific value or choose to increase the current value.
-`sound`|The name of the sound file in the app’s bundle.
-`content-available`|If you use the Newsstand, set this to 1 to start a background download.
-
-See [Push Notification REST API Guide](/sdk/push/guide/rest/) for additional reserved fields.
-
-To increase the number on the badge and play a sound:
-
-
-
-```objc
-NSDictionary *data = [NSDictionary dictionaryWithObjectsAndKeys:
- @"The Mets scored! The game is now tied 1-1!", @"alert",
- @"Increment", @"badge",
- @"cheering.caf", @"sound",
- nil];
-LCPush *push = [[LCPush alloc] init];
-[push setChannels:[NSArray arrayWithObjects:@"Mets", nil]];
-[push setData:data];
-[push sendPushInBackground];
-```
-```swift
-let channels: [String] = ["Mets"]
-
-let messageData: [String: Any] = [
- "alert": "The Mets scored! The game is now tied 1-1!",
- "badge": "Increment",
- "sound": "cheering.caf"
-]
-
-LCPush.send(data: messageData, channels: channels) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-
-
-You can also add other custom data. In the section about receiving push notifications, you can see that when the user opens your app from a push notification, the app can access this data. This would be helpful if you wanted to display a specific view controller when the user opens your app from a push notification.
-
-
-
-```objc
-NSDictionary *data = [NSDictionary dictionaryWithObjectsAndKeys:
- @"Ricky Vaughn was injured in last night's game!", @"alert",
- @"Vaughn", @"name",
- @"Man bites dog", @"newsItem",
- nil];
-LCPush *push = [[LCPush alloc] init];
-[push setChannel:@"Indians"];
-[push setData:data];
-[push sendPushInBackground];
-```
-```swift
-let channels: [String] = ["Indians"]
-
-let messageData: [String: Any] = [
- "alert": "Ricky Vaughn was injured in last night's game!",
- "name": "Vaughn",
- "newsItem": "Man bites dog"
-]
-
-LCPush.send(data: messageData, channels: channels) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-
-
-### Set Expiration Date
-
-A push notification cannot be delivered if the target device is turned off or offline. If you want to send a time-sensitive push notification that you don’t want the user to see after a while, you can set an expiration time for it.
-
-One method is to specify an expiration time. When the time expires, the server will no longer deliver the push notification.
-
-
-
-```objc
-NSDateComponents *comps = [[NSDateComponents alloc] init];
-[comps setYear:2013];
-[comps setMonth:10];
-[comps setDay:12];
-NSCalendar *gregorian =
- [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
-NSDate *date = [gregorian dateFromComponents:comps];
-
-// Send push notification with expiration date
-LCPush *push = [[LCPush alloc] init];
-[push expireAtDate:date];
-[push setMessage:@"Season tickets on sale until October 12th"];
-[push sendPushInBackground];
-```
-```swift
-let expirationDate = Date(timeIntervalSinceNow: 600)
-
-let messageData: [String: Any] = [
- "alert": "Season tickets on sale until October 12th"
-]
-
-LCPush.send(data: messageData, expirationDate: expirationDate) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-
-
-There’s a problem with the above method: since the clock on the device might not be accurate, you might get a result that’s not what you expected. There’s another method where you can set an interval and the push notification will expire after the interval:
-
-
-
-```objc
-NSTimeInterval interval = 60*60*24*7; // 1 week
-
-LCPush *push = [[LCPush alloc] init];
-[push expireAfterTimeInterval:interval];
-[push setMessage:@"Season tickets on sale until October 18th"];
-[push sendPushInBackground];
-```
-```swift
-let expirationInterval: TimeInterval = 60*60*24*7
-
-let messageData: [String: Any] = [
- "alert": "Season tickets on sale until October 18th"
-]
-
-LCPush.send(data: messageData, expirationInterval: expirationInterval) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-
-
-We recommend that you set expiration times for any push notifications you send to iOS devices. This will ensure that if a user has airplane mode enabled when you send a push notification, they will be able to receive the notification when they turn off airplane mode. See [Stackoverflow - Push notification is not being delivered when iPhone comes back online](http://stackoverflow.com/questions/24026544/push-notification-is-not-being-delivered-when-iphone-comes-back-online).
-
-## Scheduled Push Notifications
-
-You can set the time at which a push notification is sent;
-
-
-
-```objc
-NSDateComponents *comps = [[NSDateComponents alloc] init];
-[comps setYear:2013];
-[comps setMonth:10];
-[comps setDay:12];
-NSCalendar *gregorian =
- [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
-NSDate *date = [gregorian dateFromComponents:comps];
-
-LCPush *push = [[LCPush alloc] init];
-[push setPushDate:date];
-[push setMessage:@"Push this notification on 2013-10-12."];
-[push sendPushInBackground];
-```
-```swift
-let pushDate = Date(timeIntervalSinceNow: 6000)
-let messageData: [String: Any] = [
- "alert": "Push this notification at a later time."
-]
-LCPush.send(data: messageData, pushDate: pushDate) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-
-
-Scheduled push notifications can also have expiration times. To set an interval:
-
-
-
-```objc
-LCPush *push = [[LCPush alloc] init];
-[push setPushDate:date];
-[push expireAfterTimeInterval:interval];
-// Other logic
-```
-```swift
-LCPush.send(data: messageData, pushDate: pushDate, expirationInterval: expirationInterval) { /* Other logic */ }
-```
-
-
-
-### Specify a Platform
-
-For cross-platform apps, you can specify a platform to send a push notification to, such as iOS or Android:
-
-
-
-```objc
-LCQuery *query = [LCInstallation query];
-[query whereKey:@"channels" equalTo:@"suitcaseOwners"];
-
-// Notification for Android users
-[query whereKey:@"deviceType" equalTo:@"android"];
-LCPush *androidPush = [[LCPush alloc] init];
-[androidPush setMessage:@"Your suitcase has been filled with tiny robots!"];
-[androidPush setQuery:query];
-[androidPush sendPushInBackground];
-
-// Notification for iOS users
-[query whereKey:@"deviceType" equalTo:@"ios"];
-LCPush *iOSPush = [[LCPush alloc] init];
-[iOSPush setMessage:@"Your suitcase has been filled with tiny apples!"];
-[iOSPush setChannel:@"suitcaseOwners"];
-[iOSPush setQuery:query];
-[iOSPush sendPushInBackground];
-```
-```swift
-let query = LCQuery(className: "_Installation")
-query.whereKey("channels", .equalTo("suitcaseOwners"))
-
-// Notification for Android users
-query.whereKey("deviceType", .equalTo("android"))
-let messageData: [String: Any] = [
- "alert": "Your suitcase has been filled with tiny robots!"
-]
-LCPush.send(data: messageData, query: query) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-
-// Notification for iOS users
-query.whereKey("deviceType", .equalTo("ios"))
-let messageData: [String: Any] = [
- "alert": "Your suitcase has been filled with tiny apples!"
-]
-LCPush.send(data: messageData, query: query) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-
-
-## Receive Push Notifications
-
-As mentioned in [Customize Notifications](#customize-notifications), you can include any data with the push notifications you send. This data can be used to change the behavior of your app when your app is opened from a push notification. For example, when a user opens a push notification telling them they have a new friend, it would be great if they could see a picture.
-
-Because Apple limits the size of the message in a push notification, please reduce the size of the data you send in a push notification as much as possible, or the message may be truncated. See [APNs documentation](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/APNsProviderAPI.html#//apple_ref/doc/uid/TP40008194-CH101-SW1) for more information.
-
-
-
-```objc
-NSDictionary *data = @{
- @"alert": @"James commented on your photo!",
- @"p": @"vmRZXZ1Dvo" // Photo's object id
-};
-LCPush *push = [[LCPush alloc] init];
-[push setData:data];
-[push sendPushInBackground];
-```
-```swift
-let messageData: [String: Any] = [
- "alert": "James commented on your photo!",
- "p": "vmRZXZ1Dvo" // Photo's object id
-]
-
-LCPush.send(data: messageData) { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
-}
-```
-
-
-
-## Respond to Push Notification Data
-
-When your app is launched from a push notification, you can access the data in the push notification from the dictionary used by the `launchOptions` parameter of the `application:didFinishLaunchingWithOptions:` method:
-
-
-
-```objc
-- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- // ...
- if ([[UIDevice currentDevice].systemVersion floatValue] < 10.0) {
- NSDictionary *notificationPayload;
- @try {
- notificationPayload = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
- } @catch (NSException *exception) {}
-
- // Create a pointer to the Photo object
- NSString *photoId = [notificationPayload objectForKey:@"p"];
- LCObject *targetPhoto = [LCObject objectWithoutDataWithClassName:@"Photo"
- objectId:photoId];
-
- // Fetch photo object
- [targetPhoto fetchIfNeededInBackgroundWithBlock:^(LCObject *object, NSError *error) {
- // Show photo view controller
- if (!error && [LCUser currentUser]) {
- PhotoVC *viewController = [[PhotoVC alloc] initWithPhoto:object];
- [self.navController pushViewController:viewController animated:YES];
- }
- }];
- }
-}
-```
-```swift
-func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
-
- if let notification = launchOptions?[.remoteNotification] as? [AnyHashable: Any] {
- print(notification)
- }
-
- return true
-}
-```
-
-
-
-If your app is already running when a push notification arrives, you can access the data with the dictionary of the `userInfo` parameter of the `application:didReceiveRemoteNotification:fetchCompletionHandler:` method for iOS version less than 10:
-
-
-
-```objc
-/*!
- * Required for iOS 7+, Xcode 14.1 or later
- */
-- (void)application:(UIApplication *)application
- didReceiveRemoteNotification:(NSDictionary *)userInfo
- fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))handler {
- // Create empty photo object
- NSString *photoId = [userInfo objectForKey:@"p"];
- LCObject *targetPhoto = [LCObject objectWithoutDataWithClassName:@"Photo"
- objectId:photoId];
-
- // Fetch photo object
- [targetPhoto fetchIfNeededInBackgroundWithBlock:^(LCObject *object, NSError *error) {
- // Show photo view controller
- if (error) {
- handler(UIBackgroundFetchResultFailed);
- } else if ([LCUser currentUser]) {
- PhotoVC *viewController = [[PhotoVC alloc] initWithPhoto:object];
- [self.navController pushViewController:viewController animated:YES];
- } else {
- handler(UIBackgroundFetchResultNoData);
- }
- }];
-}
-```
-```swift
-func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
- // handle notification
-}
-```
-
-
-
-For iOS 10 and above, use the following delegate method to get the `userInfo`:
-
-
-
-```objc
-/**
- * Required for iOS 10+, Xcode 14.1 or later
- * The method that is called when the app receives a push notification while running in the foreground
- */
-- (void)userNotificationCenter:(UNUserNotificationCenter *)center
- willPresentNotification:(UNNotification *)notification
- withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
- NSDictionary *userInfo = notification.request.content.userInfo;
- if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
- // TODO: Handle push notification content
- NSLog(@"%@", userInfo);
- }
- // Run this method and choose whether to notify the user; you can choose Badge, Sound, or Alert
- completionHandler(UNNotificationPresentationOptionAlert);
-}
-
-/**
- * Required for iOS 10+, Xcode 14.1 or later
- * The method that is called when the app is in the background or not running and the user opens a push notification
- */
-- (void)userNotificationCenter:(UNUserNotificationCenter *)center
-didReceiveNotificationResponse:(UNNotificationResponse *)response
- withCompletionHandler:(void (^)())completionHandler {
- NSDictionary * userInfo = response.notification.request.content.userInfo;
- if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
- // TODO: Handle push notification content
- NSLog(@"%@", userInfo);
- }
- completionHandler();
-}
-```
-```swift
-func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
- // handle notification
-}
-
-func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
- // handle notification
-}
-```
-
-
-
-## Clear the Badge
-
-The badge is often cleared when the user opens or exits the app.
-
-
-
-```objc
-- (void)applicationDidBecomeActive:(UIApplication *)application {
- // Clear the local badge
- [application setApplicationIconBadgeNumber:0];
- // Clear currentInstallation’s badge
- [LCInstallation defaultInstallation].badge = 0;
- [[LCInstallation defaultInstallation] saveInBackgroundWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
- if (succeeded) {
- // save succeeded
- } else if (error) {
- NSLog(@"%@", error);
- }
- }];
-}
-```
-```swift
-override func applicationDidBecomeActive(_ application: UIApplication) {
- // Clear the local badge
- application.applicationIconBadgeNumber = 0
- // Clear currentInstallation’s badge
- LCApplication.default.currentInstallation.badge = 0
- LCApplication.default.currentInstallation.save { (result) in
- switch result {
- case .success:
- break
- case .failure(error: let error):
- print(error)
- }
- }
- }
-```
-
-
-
-
-To learn more about push notifications, check out [Apple’s Local and Remote Notifications Overview](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/Introduction.html#//apple_ref/doc/uid/TP40008194-CH1-SW1).
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/overview.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/overview.mdx
deleted file mode 100644
index 20f01ba4e..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/overview.mdx
+++ /dev/null
@@ -1,122 +0,0 @@
----
-title: Push Notification Overview
-sidebar_label: Overview
-sidebar_position: 0
-slug: /sdk/push/guide/overview/
----
-
-
-
-
-
-import {Conditional} from '/src/docComponents/conditional';
-
-
-Push notifications allow you to deliver messages to users instantly and stay in touch with them, helping to increase user retention and improve the user experience. TapTap Developer Services provides a unified service that allows you to send push notifications to both Android and iOS users.
-
-In addition to sending push notifications using the iOS and Android SDK, you can also trigger them using the REST API.
-
-
-
-
-Before you can use the Push Notification service, please make sure you have enabled the service on **App > Settings > Security**. You may need to wait up to 3 minutes after clicking on the button before using the service.
-
-
-
-
-
-Before you can use the Push Notification service, please make sure you have enabled the service on **Developer Center > Your game > Game Services > Configuration**. You may need to wait up to 3 minutes after clicking on the button before using the service.
-
-
-
-## Glossary
-
-### Installation
-
-An Installation is a unique identifier for a device that accepts push notifications from your application. Each Installation corresponds to an entry in the `_Installation` table. An Installation is essentially an LCObject that contains the following properties:
-
-Name|Applicable platform|Description
----|---|---
-badge|iOS|The badge at the top of the app icon that shows the number of new notifications.
-channels| |The channel to which the device is subscribed. It can contain only uppercase and lowercase letters, numbers, underscores (`_`), hyphens (`-`), equal signs (`=`), and Chinese characters.
-deviceProfile||deviceProfile is used to specify the name of the certificate or configuration used by the current device if there are multiple iOS certificates or Android mixpush configurations. Its value must match the certificate name or configuration name set up on **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Settings**, or push notifications won’t be delivered successfully. The value of `deviceProfile` must start with a letter and can only contain uppercase and lowercase letters, numbers, and underscores. You can also leave this field empty. deviceProfile is a special field that only supports `equals` queries.
-deviceToken|iOS|A unique identifier used by APNs.
-apnsTopic|iOS|This field must be configured if you are using push notification based on Token Authentication. The iOS SDK automatically uses the application’s bundle ID as apnsTopic, but you will need to set this field manually in the following situations: 1. The version of the iOS SDK you are using is below v4.2.0; 2. You are not using the iOS SDK (for example, you are using React Native); 3. You are using a different topic than the bundle ID.
-deviceType| |The type of the device. Can be `ios` or `android`.
-installationId|Android|A unique identifier generated by the SDK for each Android device.
-timeZone| |A string representing the timezone of the device.
-
-### Notification
-
-Each Notification corresponds to a record displayed on **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Push records**, which corresponds to a push notification being sent. It contains the following properties:
-
-Name|Applicable platform|Description
----|---|---
-notificationId| | The ID of the push notification.
-msg| |A JSON object that contains the contents of the push notification. See [消息内容参数](/sdk/push/guide/rest#消息内容参数) for more information.
-invalidTokens|iOS|The number of [INVALID TOKEN](https://developer.apple.com/library/mac/technotes/tn2265/_index.html#//apple_ref/doc/uid/DTS40010376-CH1-TNTAG32) errors returned by APNs. **If this number seems high, please check the validity of the certificate.**
-prod|iOS|The environment of the certificate being used. **dev** means the development environment and **prod** means the production environment.
-status| |The status of the push notification. **in-queue** means the push notification is in the queue. **done** means the push notification has been delivered. **scheduled** means the push notification is scheduled and is waiting to be triggered.
-devices| |The number of target devices for this push notification, which is the number of valid devices queried from the `_Installation` table when this push notification request is being processed. This may not be the same as the number of devices that actually received the push notification. A device is “valid” if its `valid` property in the `_Installation` table is `true` and its `updatedAt` is within 3 months. Target devices may include inactive devices on which your application has been uninstalled. These devices may not be able to receive the push notification.
-successes| |The number of devices to which the push notification was successfully sent. For push notifications sent directly to Android devices, this means that the devices received the push notification. For push notifications sent to iOS devices or sent to Android devices via mixpush, this means that the push notification was sent to APNs or the corresponding mixpush platforms. The number of devices to which the push notification was successfully sent through a particular channel can be found in the `[lc/ios/fcm/hms/mi/oppo/vivo/meizu]Successes` field.
-where| |The conditions used to query the `_Installation` table. Devices that match the conditions will receive the push notification.
-errors| | If an error occurred with the push notification, this is the error message.
-from-service| | **push** means the push notification is triggered directly; **rtm** means the push notification is triggered by a message sent through the Instant Messaging service.
-push-time| | If this is a scheduled push notification, this is the time the push notification will be triggered.
-
-When a push notification is triggered, the server first looks up devices in the `_Installation` table that match the query condition, and then pushes the message to the devices. Because each `_Installation` is a key-value object that can contain custom attributes, you can implement complex conditions for sending push notifications, such as sending push notifications to users who are subscribed to specific channels or who are within a specific geographic area. You can also send push notifications to specific users.
-
-Note the difference between **devices** and **successes**. If **devices** is 0, it means that there are no devices that match the conditions you specified. In this case, you need to check the conditions and see if they need to be changed. If **devices** is not 0, its value indicates the number of devices found that match the conditions you specified, but it’s not guaranteed that those devices will receive push notifications. It’s likely that **successes** will be less than **devices**. If there are a large number of inactive devices, there may be a large difference between **successes** and **devices**.
-
-To prevent a device from receiving push notifications, change the `valid` attribute of the object for that device in the `_Installation` table to `false`.
-
-Note that we only keep push records for one week, and will remove push records that were created more than a week ago. Even if a push record is removed, the push notifications it triggered will still be valid (and received by the target users) as long as they haven’t expired. For more information on how to set expiration times for push notifications, see [推送 REST API 使用指南](/sdk/push/guide/rest/).
-
-## Unity
-
-See [Unity Push Notification Guide](/sdk/push/guide/unity/).
-
-## iOS
-
-See [iOS Push Notification Guide](/sdk/push/guide/ios/).
-
-## Android
-
-With the stricter permission controls enforced by Android, the deliverability of push notifications sent through the Push Notification service’s own channels has been negatively impacted.
-Therefore, we recommend that you send push notifications using mixpush. It integrates the interfaces provided by major Chinese handset vendorsFCM so that you can trigger push notifications using a simple API.
-See [Android Mixpush Guide](/sdk/push/guide/android-mixpush/) for more information.
-
-To learn more about Push Notification’s own channels, see [Android Push Notification Guide](/sdk/push/guide/android/).
-
-## Sending Push Notifications With REST API
-
-See [Push Notification REST API](/sdk/push/guide/rest/).
-
-## Sending Push Notifications With JavaScript SDK in Cloud Engine
-
-The JavaScript SDK also provides an interface for sending push notifications, although it is primarily used in Cloud Engine. See the SDK’s API documentation ([AV.Push](https://leancloud.github.io/javascript-sdk/docs/AV.Push.html)) for more information.
-Here are two simple examples:
-
-To send push notifications to all devices subscribed to the `public` channel:
-
-```js
-AV.Push.send({
- channels: [ 'public' ],
- data: {
- alert: 'public message'
- }
-});
-```
-
-To send push notifications to devices whose corresponding objects in the `_Installation` table match certain conditions, you can specify an `AV.Query` object as the `where` condition. For example, to send a push notification to an Android device that has a specific `installationId`:
-
-```js
-const query = new AV.Query('_Installation');
-query.equalTo('installationId', installationId);
-AV.Push.send({
- where: query,
- data: {
- alert: 'Public message'
- }
-});
-```
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/push-faq.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/push-faq.mdx
deleted file mode 100644
index 452f6cd3f..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/push-faq.mdx
+++ /dev/null
@@ -1,206 +0,0 @@
----
-title: Push Notification FAQ
-sidebar_label: FAQ
-sidebar_position: 11
-slug: /sdk/push/guide/push-faq/
----
-
-
-
-
-
-### Why is the number of successful devices less than the target number of devices?
-
-The "target number of devices" refers to the number of valid devices that meet the conditions of this push request, and the "number of successful devices" refers to the number of devices that were successfully reached by this push. There are several situations where devices are not reached:
-
-- The application in the Android device is killed and is offline on the network. In this case, the number of successful devices will gradually increase after waiting a while for the devices to come online.
-- The Android user deleted or reinstalled the application to generate new device data, and the previous invalid data is included in the "target device count".
-- The iOS user deleted or reinstalled the app, and the previous invalid data is included in the target device count, and these numbers are the number of invalidTokens.
-
-### Why can I only send messages to devices that have been active in the last three months? Is it possible to send to all devices?
-
-We only allow developers to send messages to devices that have an **updatedAt** value of **within the last three months** in the `_Installation` table. The reason for this is as follows:
-
-- Devices that have been inactive for three months have a very low probability of users opening the push, and we treat these inactive devices as invalid.
-- Cost reasons. Sending push notifications to all devices will tremendously increase the number of invalid devices, thus consuming a large amount of cloud resources, and one of the impacts that can be perceived by users is the push time.
-
-Due to the above considerations, by default we can only send messages to devices that have been active in the last three months. If you really need to send messages to all devices, you can contact us to upgrade to the Enterprise version of the service and we will create a separate cluster for your application to provide the push service.
-
-### Why is the number of target devices in the push record 0?
-
-In the push record of the dashboard, "number of target devices" refers to the number of valid devices that meet the conditions of this push request. If the value is 0, please check **the conditions of the push query and whether the target devices are valid**.
-
-### Why is the contents of the push record empty?
-
-In the push record of the dashboard, if different message contents are set for different target device types, the "Contents" column will be displayed empty. You need to click the "ID" of the message to open the push details to see the specific contents.
-
-### What is the delivery rate for push notifications?
-
-There is no industry standard for delivery rate. We have tested that the delivery rate of messages from online users is basically 100%. Our SDK has heartbeat and reconnect features to maintain a long connection to the push server as much as possible to improve the speed and reliability of messages reaching users' phones.
-
-### When sending push notifications with iOS Token Authentication certificates, is there a difference between test and production environments?
-
-Yes, the prod parameter is used to distinguish between test and production environments. The same key can send messages to both the test and production environments.
-
-However, the same deviceToken can only be used to successfully push to one environment (either the production environment or the test environment). When sending a push notification to a deviceToken, if it works for the dev, you will get the "invalid Tokens" error for the prod.
-
-### Some iOS devices are not receiving pushes, and when I check the push log in the console, I see that the number of invalidTokens is greater than 0. What is going on?
-
-The number of invalidTokens consists of two parts:
-* The number of invalidTokens increases if the selected device does not match the selected certificate, for example, if you are using a development certificate to push to a device with a production certificate. Please check if the APNS certificate is expired and if the correct certificate type is used.
-* The target device has removed or reinstalled the corresponding application.
-* The invalidTokens error is also reported if the team ID is not uploaded when saving the DeviceToken.
-* When uploading a certificate to the dashboard using Token Authentication, an invalidTokens error will also be reported if the TeamID or Topics is entered incorrectly (Topics is the Bundle ID of the application).
-
-### Can I customize the Receiver for Android notifications so that no notification is shown?
-
-Yes. Please refer to the [Push Notification Guide](/sdk/push/guide/rest/#消息内容参数).
-
-If you want to customize the receiver, you must include a custom action in the message data. When the client receives the message, it will send an intent event with the action value you defined, and your receiver must also include an `intent-filter` to catch the intent event with the action value.
-
-### How long is push history kept?
-
-Push history is kept for 7 days. Push history prior to 7 days cannot be reviewed.
-
-### Will both devices receive push messages if the same account is signed in on both devices?
-
-The push is based on the push query criteria, and the target device is found in the _Installation table. If both devices are included in the query criteria, both devices receive the push.
-
-For the Instant Messaging service, if single sign-on is not enabled, when a user logs in on two devices, if the user is offline, the server will send push notifications to both devices.
-
-### The console push log shows a successful push, but the Android device did not actually receive the push. What is the reason for this?
-
-If the number of successes returned is 1, it means that a response must have been received from the SDK confirming receipt of the message. This means that the push message must have reached the device.
-It is recommended to check if the push uses the custom Receiver (whether there is an action field in the message) where the message arrives and the SDK passes it directly to the custom Receiver that completes the push notification. In this case, you must check the custom Receiver implementation logic to troubleshoot why the notification does not pop up when the message arrives.
-
-### Workaround for older versions of the Objective-C SDK not receiving pushes in the iOS 13 environment.
-
-In iOS 13, older versions of the Objc SDK (<= 11.6.6) are unable to upload a valid device token due to Apple's change in the API of the underlying framework.
-
-The workaround is to upgrade the SDK to v11.6.7 and above and save the device token as described in [Save the Token](/sdk/push/guide/ios/#save-the-token).
-
-The workaround for older versions of the Objective-C SDK (<= 11.6.6) is to upload the device token as follows:
-
-```
-NSUInteger dataLength = deviceToken.length;
-if (dataLength > 0) {
- const unsigned char *dataBuffer = deviceToken.bytes;
- NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
- for (int i = 0; i < dataLength; ++i) {
- [hexString appendFormat:@"%02.2hhx", dataBuffer[i]];
- }
- [installation setDeviceToken:[hexString copy]];
- [installation saveInBackground];
-}
-```
-
-### I get the error "Some pushes are rejected by APNs due to BadDeviceToken" when sending push notifications to iOS devices.
-
-This error occurs because the push environment is incorrect. For example, if the push is sent to a device in the test environment using the prod parameter (i.e. `"prod": "dev"`), the device in the production environment will not receive the push.
-Similarly, if you specify to push to the production environment (i.e., `"prod": "prod"`), devices in the test environment will not receive the push.
-For the push environment for iOS, see [Environments](/sdk/push/guide/ios/#environments).
-
-### How do I select a push certificate for iOS?
-The push interface has a parameter called **prod**.
-**This parameter is only valid for iOS push.**
-
-* When sending iOS pushes using Token Authentication, this parameter is used to set whether to send the push to the development (dev) or production (prod) environment of the APNs.
-* For iOS pushes using certificate authentication, this parameter sets whether to use development (dev) or production (prod) certificates. When using the certificate authentication method, if the device has a deviceProfile set in the Installation table, we give priority to pushing with the certificate specified by the deviceProfile.
-
-### How is the deviceToken stored correctly for iOS push?
-
-The device token changes when a user erases their device, restores the app from backup, or installs the app on a new device. To solve this problem, Apple recommends that the app requests the device token of the APNs when opening, and then sets and saves the token. Also, our system keeps track of the update times (`updatedAt`) of all Installation objects and removes the objects that haven’t been updated in a while.
-Sample code can be found in: [iOS Push Guide](/sdk/push/guide/ios/#save-the-token).
-
-### For iOS, what can I do if the app does not receive a push when it is in the foreground?
-
-The push log will show that the push was successfully delivered, but the push is not received on the mobile side. This is because, by default, the push is not displayed in the notification bar when the iOS app is in the foreground. If the app still needs to display the push, you must use the UNUserNotificationCenterDelegate delegate method `userNotificationCenter:willPresentNotification:withCompletionHandler:` to handle how the notification is displayed.
-
-Available methods for displaying the push notification include:
-* UNNotificationPresentationOptionBadge: Adds the value of badge to the application icon.
-* UNNotificationPresentationOptionBanner: Presents the notification with a banner.
-* UNNotificationPresentationOptionList: Display the notification in the notification center.
-* UNNotificationPresentationOptionSound: Play the sound for the notification.
-
-The sample code to display the notification as a banner is as follows:
-
-```objc
-#import "AppDelegate.h"
-#import
-#import
-
-@interface AppDelegate ()
-@end
-@implementation AppDelegate
-
-- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
-
- //The code to initialize the SDK is omitted
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
- //Set the delegate object
- center.delegate = self;
- return YES;
-}
-- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{
- completionHandler(UNNotificationPresentationOptionBanner);
-}
-```
-
-### How is _Installation associated with the user id in _User in the offline push notification service?
-
-After a user logs in to the Instant Messaging system, the server stores the user's `clientId` in the `channels` field of the device's `_Installation`, completing the association. If the user is offline and there are offline messages to push, the server will go to the `_Installation` table and find the device with the `channels` field containing the target `clientId` to complete the push.
-
-### If there are two applications on the same device, how do I push to the specified application?
-
-The _Installation table holds the installation information generated on the device. A new field can be added to the _Installation table to distinguish between the different applications. When you push a message, if you choose to push to all users, both applications will receive the push. If you want to push to a specific application, you can refer to the push document [Send to Specific Users](/sdk/push/guide/android/#send-to-specific-users) to push only to the specified device.
-
-### What does the valid field in _Installation refer to and why is it false?
-
-`valid` indicates whether the device record is currently valid. `false` means the record is invalid. A possible reason for this is that push has not been used for a long time, or the device is not registered.
-
-For example, if an Android device does not execute the following code to [Enable the Push Notification Service](/sdk/push/guide/android/#enable-the-push-notification-service), the value of valid will always be false.
-
-```
-// Set the default opened Activity
-PushService.setDefaultPushCallback(this, PushDemo.class);
-```
-If a device does not want to receive push notifications, you can also set the valid field of the corresponding installation object in the _Installation table to false.
-
-## Troubleshooting
-
-Troubleshooting push notifications can be tricky because there are many device- and network-related steps involved in the push process, and the calls are asynchronous. Here are some tips to help you troubleshoot push issues.
-
-### Query Push Results
-
-All messages sent through the `/push` interface can be viewed in the push log in the dashboard's message menu. Each time `/push` is invoked, a new push record is created to represent a push. For the meaning of the properties in this table, see [the Notification table](/sdk/push/guide/overview/#notification).
-
-The `/push` interface returns the `objectId` of the newly created push record, so you can look up the results of the push in the push record based on the ID.
-
-### Suggestions for iOS Troubleshooting
-
-Some suggestions for troubleshooting iOS push issues:
-
-* Make sure you are using the correct **Bundle Identifier** in your project's Info.plist file.
-* Make sure the correct **provisioning profile** is set in **Project** > **Build Settings**.
-* Try cleaning the project and restarting Xcode.
-* Try going to [Apple Developer](https://developer.apple.com/account/overview.action) to regenerate the **provisioning profile**, changing the Apple ID, changing it back, and regenerating the profile. You'll need to reinstall the provisioning profile and reset it in **Project** > **Build Settings**.
-* Open XCode Organizer and remove all expired and unused provisioning profiles from your computer and iOS devices.
-* If the app builds and runs fine, but you still don't receive pushes, make sure your app has permission to receive pushes enabled by checking in **Settings** > **Notifications** > **Your App** on your iOS device.
-* If the permissions are also OK, make sure you are using the correct **provisioning profile**. Package your application. If you uploaded a development certificate and pushed with a development certificate, you must build your application using the **Development Provisioning Profile**. If you are uploading a production certificate and pushing with a production certificate, make sure your application is packaged using the **Distribution Provisioning Profile** signature. Both **Ad Hoc** and **App Store Distribution Provisioning Profile** can be used to receive messages sent with a production certificate.
-* When enabling push to an existing Apple ID, remember to regenerate the **provisioning profile** and update it in XCode Organizer.
-* Production push certificates must be enabled and generated before you submit your app to the App Store, or you will need to resubmit it to the App Store.
-* Please test the production environment push using the Ad Hoc Profile before submitting your app to the App Store. This is the configuration closest to the one used by the App Store.
-* Check the `devices` and `status` in the push log to make sure the push status and number of receiving devices are normal.
-* Check the `invalidTokens` field in the push log. If the number is abnormally high, the certificate may have been misselected and does not match the provisioning profile of the device build.
-* It is recommended that you use a serial queue for the installation to avoid a crash when saving the installation due to multi-threaded writes. You can see the [VoIP project in the Swift demo][swift-voip-demo].
-
-[swift-voip-demo]: https://github.com/leancloud/swift-sdk-demo#voip
-
-### Suggestions for Android Troubleshooting
-
-Some suggestions for troubleshooting Android push issues:
-
-* Make sure the device has called `AVInstallation` correctly to store device information in the _Installation table.
-* You can check if the device is online with `installationId` in the dashboard at **Push Notification** > **Devices**.
-* Make sure that `com.avos.avoscloud.PushService` is added to the AndroidManifest.xml file.
-* If you are using a custom Receiver, make sure you declare your Receiver in the AndroidManifest.xml and make sure the action is consistent in the data.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/rest.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/rest.mdx
deleted file mode 100644
index a6f2a00c0..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/rest.mdx
+++ /dev/null
@@ -1,823 +0,0 @@
----
-title: Push Notification REST API
-sidebar_position: 8
-slug: /sdk/push/guide/rest/
----
-
-
-
-
-
-import { Conditional } from "/src/docComponents/conditional";
-
-Once the application is installed on the user's device, the SDK automatically generates an Installation object when you use the push feature. The Installation object contains all the information needed to send push notifications. You can use the REST API to push through the Installation object.
-
-
-
-
-
-The base URL for the request can be found at **App > Settings > App keys > Server URLs**.
-For POST and PUT requests, the request body must be in JSON format and the Content-Type of the HTTP header must be set to `application/json`.
-
-
-
-
-
-
-The base URL for the request can be found at **Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings**.
-For POST and PUT requests, the request body must be in JSON format and the Content-Type of the HTTP header must be set to `application/json`.
-
-
-
-Requests are authenticated by the key-value pairs contained in the HTTP header, as described in the [Request Format](/sdk/storage/guide/rest/#request-format) section of the *Data Storage REST API*.
-
-## Installation
-
-You can add Installation objects to the cloud using the REST API.
-Using the REST API also allows you to do things that the client SDK cannot do, such as querying all Installations to find a collection of subscribers to a channel.
-
-
-
-
-
URL
-
HTTP
-
Function
-
-
-
-
-
/1.1/installations
-
POST
-
Upload Installation
-
-
-
/1.1/installations/<objectId>
-
GET
-
Get Installation
-
-
-
/1.1/installations/<objectId>
-
PUT
-
Update Installation
-
-
-
/1.1/installations
-
GET
-
Query Installation
-
-
-
/1.1/installations/<objectId>
-
DELETE
-
Delete Installation
-
-
-
-
-### Add Installation
-
-Creating an Installation object is similar to creating a normal object, except that different platforms have different fields.
-
-Upon successful creation, the HTTP return value is **201 Created** and the Location header contains the URL of the new Installation:
-
-```sh
-Status: 201 Created
-Location: https://{{host}}/1.1/installations/51ff1808e4b074ac5c34d7fd
-```
-
-The body returned is a JSON object, including the objectId and createdAt, the timestamp of the created object.
-
-```json
-{
- "createdAt": "2012-04-28T17:41:09.106Z",
- "objectId": "51ff1808e4b074ac5c34d7fd"
-}
-```
-
-#### DeviceToken
-
-iOS devices typically use DeviceToken to uniquely identify a device.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "deviceType": "ios",
- "deviceToken": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
- "channels": [
- "public", "protected", "private"
- ]
- }' \
- https://{{host}}/1.1/installations
-```
-
-#### installationId
-
-For Android devices, the SDK will automatically generate a uuid as the installationId to store in the cloud.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "deviceType": "android",
- "installationId": "12345678-4312-1234-1234-1234567890ab",
- "channels": [
- "public", "protected", "private"
- ]
- }' \
- https://{{host}}/1.1/installations
-```
-
-The `installationId` must be unique within the application.
-
-### Get Installation
-
-You can get an Installation object by requesting the URL represented by the Location at the time of creation using the GET method. For example, to get the object created above:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- https://{{host}}/1.1/installations/51ff1808e4b074ac5c34d7fd
-```
-
-The returned JSON object contains all of the fields provided by the user, as well as the createdAt, updatedAt, and objectId fields:
-
-```json
-{
- "deviceType": "ios",
- "deviceToken": "abcdefghijklmnopqrstuvwzxyrandomuuidforyourdevice012345678988",
- "channels": [
- ""
- ],
- "createdAt": "2012-04-28T17:41:09.106Z",
- "updatedAt": "2012-04-28T17:41:09.106Z",
- "objectId": "51ff1808e4b074ac5c34d7fd"
-}
-```
-
-### Update Installation
-
-The Installation object can be updated by sending a PUT request to the appropriate URL. For example, to subscribe to a push channel, set the `channels` property to:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "deviceType": "ios",
- "deviceToken": "abcdefghijklmnopqrstuvwzxyrandomuuidforyourdevice012345678988",
- "channels": [
- "",
- "foo"
- ]
- }' \
- https://{{host}}/1.1/installations/51ff1808e4b074ac5c34d7fd
-```
-
-To unsubscribe from a channel:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "channels": {
- "__op":"Remove",
- "objects":["customer"]
- }
- }' \
- https://{{host}}/1.1/installations/51ff1808e4b074ac5c34d7fd
-```
-
-`channels` is essentially an array property, so you can use standard array operations.
-
-Another example is adding custom attributes:
-
-```sh
-curl -X PUT \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- -d '{
- "userObjectId": ""
- }' \
- https://{{host}}/1.1/installations/51ff1808e4b074ac5c34d7fd
-```
-
-### Query Installation
-
-You can retrieve multiple Installations at once by sending a GET request to the root URL of installations. This feature is not available in the SDK.
-
-Without any URL parameters, a GET request will list all Installations:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.1/installations
-```
-
-The result field of the returned JSON object contains all results:
-
-```json
-{
- "results": [
- {
- "deviceType": "ios",
- "deviceToken": "abcdefghijklmnopqrstuvwzxyrandomuuidforyourdevice012345678988",
- "channels": [
- ""
- ],
- "createdAt": "2012-04-28T17:41:09.106Z",
- "updatedAt": "2012-04-28T17:41:09.106Z",
- "objectId": "51ff1808e4b074ac5c34d7fd"
- },
- {
- "deviceType": "ios",
- "deviceToken": "876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9",
- "channels": [
- ""
- ],
- "createdAt": "2012-04-30T01:52:57.975Z",
- "updatedAt": "2012-04-30T01:52:57.975Z",
- "objectId": "51fcb74ee4b074ac5c34cf85"
- }
- ]
-}
-```
-
-All queries on normal objects work on Installation objects, so see the previous queries section for details. An array query of channels allows you to find all devices subscribed to a particular push channel.
-
-For security reasons, the Installation lookup permission is not open by default in the cloud, so this interface typically requires master key authentication.
-
-### Delete Installation
-
-To remove an Installation from LeanCloud, you can send a DELETE request to the appropriate URL, which is also not available in the client SDK. Example:
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- https://{{host}}/1.1/installations/51fcb74ee4b074ac5c34cf85
-```
-
-For security reasons, the Installation deletion permission is not open by default in the cloud, so this interface typically requires master key authentication.
-
-### Automatic Installation Expiration and Cleanup
-
-Whenever a user opens an application, we update the `updatedAt` timestamp in the `_Installation` table for that device. If the user has not updated the `updatedAt` timestamp in the `_Installation` table for a long time, it means that the user has not opened the application for a long time. If the user has not opened the application for more than 90 days, we will remove the user's record from the `_Installation` table. But don't worry, if the user opens the application again, a new Installation will be automatically created for pushing.
-
-For iOS devices, there is another expiration mechanism in addition to the one described above. When we get feedback from Apple's push service that a device's deviceToken has expired, we also remove the device from the `_Installation` table, mark the expired deviceToken as invalid, and discard any subsequent messages sent to the deviceToken.
-
-### API Interfaces at a Glance
-
-Path|Method|Description
----|---|---
-/1.1/push|POST|Send a push notification
-/1.1/notifications|GET|Get push records
-/1.1/notifications/:notification_id|GET|Get a push record by its ID
-/1.1/notifications/:notification_id|DELETE|Delete a push record by its ID
-/1.1/scheduledPushMessages|GET|Get all scheduled push notifications
-/1.1/scheduledPushMessages/:id|DELETE|Delete a scheduled push notification by its ID
-
-### master key Verification
-
-If **Prevent clients from sending push notifications** is checked in **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Settings**,
-you will need to pass the **master key** to send pushes, which will prevent the client from being able to push messages to any target device in the app without restriction.
-This restriction is enabled by default.
-We recommend that all users enable this restriction.
-
-### Message Content Parameters
-
-#### iOS Device Push Message Content Parameters
-
-For the specific meaning of the attributes within data and alert in iOS devices, please refer to:
-1. [Apple's official documentation on the Payload Key](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification),
-2. [Apple's official documentation on the Request Header](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html), and
-3. [Apple's official documentation on the UserNotifications](https://developer.apple.com/documentation/usernotifications).
-
-Here are some specific descriptions of each property:
-
-#### Properties of data for iOS Devices
-
-Name|Format|Constraint|Description
----|---|---|---
-alert|Plain string or JSON string|Required|The content of the message. If the target device contains only iOS devices, it can also be of the JSON type; the supported properties for the JSON type are described below.
-title|String|Optional|The title of the push content. If the alert field is a string, you can add the title here, but if the alert is of JSON type, you do not need to provide this field.
-category|String|Optional|Notification type.
-thread-id|String|Optional|Name of the notification category.
-badge|Number|Optional|The number of unread messages displayed on the badge above the application icon, either a number or the string "Increment" (case sensitive).
-sound|Plain string or JSON string|Optional|The sound of the push notification. See the section describing JSON objects below for more details.
-content-available|Number|Optional|Set to 1 to start a background download when using the Newsstand.
-mutable-content|Number|Optional|Used to support UNNotificationServiceExtension. Set to 1 to enable it.
-collapse-id|String|Optional|Corresponds to the apns-collapse-id parameter of the APNs request header, which is used to collapse multiple notifications as described in Apple's official request header documentation linked below.
-apns-priority|Number|Optional|Can only be 10 or 5. Corresponds to the apns-priority parameter of the APNs request header, which is used to control whether notifications are sent in power saving mode. Please click the link below for Apple's official request header documentation for details.
-apns-push-type|String|Optional|Used to set the push display type. Supported on iOS 13 or watchOS 6 or higher. Can only be "background" or "alert". Default is "alert".
-url-args|String|Optional|A list for Safari notifications. See the APNs documentation for details on the url-args parameter.
-target-content-id|String|Optional|See the APNs documentation for details on the target-content-id parameter.
-Custom attribute|Custom type|Optional|Custom attributes added by the user, with arbitrary field names and field types.
-
-Example:
-
-```json
-{
- "alert": "hi"
-}
-```
-
-#### Properties of alert for iOS Devices
-
-iOS devices support localized `alert` message pushing by replacing the above `alert` parameter from a string to JSON consisting of localized message pushing attributes:
-
-Name|Format|Constraint|Description
----|---|---| ---
-title|String|Optional|The title of the notification content.
-title-loc-key|String list|Optional|See Apple's push notification localization guide for details.
-title-loc-args|String list|Optional|See Apple's push notification localization guide for details.
-subtitle|String|Optional|The subtitle of the notification content.
-subtitle-loc-key|String|Optional|See Apple's push notification localization guide for details.
-subtitle-loc-args|String list|Optional|See Apple's push notification localization guide for details.
-body|String|Optional|The body of the message.
-action-loc-key|String|Optional|See Apple's push notification localization guide for details.
-loc-key|String|Optional|See Apple's push notification localization guide for details.
-loc-args|String list|Optional|See Apple's push notification localization guide for details.
-launch-image|String|Optional|Sets the name of the image file to launch when the notification is clicked.
-summary-arg|String|Optional|Used to set the summary.
-summary-arg-count|Number|Optional|Used to set the number of summary arguments.
-
-Example:
-
-```json
-{
- "alert": {
- "title": "A message"
- "body": "A body"
- }
-}
-```
-
-#### Properties of sound for iOS Devices
-
-iOS supports setting the push sound through the sound parameter, either with a string indicating the filename of a sound, pointing to a sound file that exists in the app, or with a JSON object:
-
-Name|Format|Constraint|Description
----|---|---|---
-name|String|Optional|Sound filename, pointing to a sound file that exists in the application.
-critical|Boolean|Optional|Set to true to use the "Critical" sound. Default is false.
-volume|Number|Optional|The volume of the sound. Must be a decimal number between 0 and 1.
-
-Example:
-
-```json
-{
- "alert": "New weixin message.",
- "badge": 9,
- "sound": "biubiubiu.aiff"
-}
-```
-
-```json
-{
- "alert": "You spent $1.99",
- "sound": {
- "name": "ding.aiff",
- "volume": "0.8"
- }
-}
-```
-
-#### Additional Notes for iOS Devices
-
-We support the construction of push parameters as described in the official Apple documentation above. For example:
-
-```json
-{
- "aps": {
- "alert": "New weixin message.",
- "badge": 9,
- "sound": "biubiubiu.aiff"
- }
-}
-```
-
-For a more detailed description, see the following example:
-
-```json
-{
- "aps": {
- "alert": {
- "title": "String; the title of the push content",
- "title-loc-key": "A list of strings; see Apple's note on localizing push notifications for details",
- "title-loc-args": "A list of strings; see Apple's note on localizing push notifications for details",
- "subtitle": "String; the subtitle of the push content",
- "subtitle-loc-key": "String; see Apple's note on localizing push notifications for details",
- "subtitle-loc-args": "A list of strings; see Apple's note on localizing push notifications for details",
- "body": "String, the body of the message",
- "action-loc-key": "String; see Apple's note on localizing push notifications for details",
- "loc-key": "String; see Apple's note on localizing push notifications for details",
- "loc-args": "A list of strings; see Apple's note on localizing push notifications for details",
- "launch-image": "String; the name of the image file to launch when the notification is clicked",
- "summary-arg": "String; used to set the summary",
- "summary-arg-count": "Number; used to set the number of summary arguments"
- },
- "category": "String; notification type",
- "thread-id": "String; name of the notification category",
- "badge": "Number; the number of unread messages displayed on the badge above the application icon, either a number or the string 'Increment' (case sensitive)",
- "sound": "Plain string or JSON string; the sound of the push notification",
- "content-available": "Number; set to 1 to start a background download when using the Newsstand",
- "mutable-content": "Number; used to support UNNotificationServiceExtension; set to 1 to enable it"
- },
- "collapse-id": "String; corresponds to the apns-collapse-id parameter of the APNs request header, which is used to collapse multiple notifications as described in Apple's official request header documentation linked below",
- "apns-priority": "Number; can only be 10 or 5; corresponds to the apns-priority parameter of the APNs request header, which is used to control whether notifications are sent in power saving mode; please click the link below for Apple's official request header documentation for details",
- "apns-push-type": "String; used to set the push display type; supported on iOS 13 or watchOS 6 or higher; can only be 'background' or 'alert'; default is 'alert'",
- "custom-key": "Custom attributes added by the user, with arbitrary field names and field types"
-}
-```
-
-#### Android Device Push Message Content Parameters
-
-For Android devices, the default notification message content parameter supports the following properties:
-
-Name|Format|Constraint|Description
----|---|---|---
-alert|String|Required|The content of the message.
-title|String|Optional|The title of the push content.
-silent|Boolean|Optional|Whether to send the push notification as a pass-through message or as a notification bar message. The default is false, i.e. `notification bar message`.
-action|String|Optional|The action name provided when the Receiver was registered. Set this only if the Receiver needs to be customized.
-Custom attribute|Custom type|Optional|Custom attributes added by the user, with arbitrary field names and field types.
-
-Example:
-
-```json
-{
- "alert": "Hi Ming, you have guests at home. Come home for dinner!",
- "title": "Ming, you received a WeChat message."
-}
-```
-
-```json
-{
- "alert": "You received $1.99.",
- "my-custom-key": "my-custom-value"
-}
-```
-
-### Push by Query Criteria
-
-This interface is used to send a push message to all valid device records in the _Installation table that match the query criteria, based on the query criteria provided. For example, the following code sends a push message containing `Hello from LeanCloud` to all valid devices that contain the value `public` in the `channels` field of the _Installation table.
-
-Note that this interface limits the requested HTTP body size to 4096 bytes, i.e., the result of JSON serialization of any parameters you pass to this interface cannot exceed this limit.
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{
- "where": {"channels" : "public"},
- "data": {"alert" : "Hello from LeanCloud"}
- }' \
- https://{{host}}/1.1/push
-```
-
-The parameters supported by this interface are:
-
-Name| Constraint | Description
----|--- | ---
-data| **Required**| The content of the push. JSON object. See [Message Content Parameters](#message-content-parameters).
-where| Optional | The query condition used to retrieve objects from the `_Installation` table. JSON object. If the query condition contains a special type of data that must be encoded, such as date or binary, the query condition must contain the encoded data. For example, if the `createdAt` field is greater than a certain time, the where condition must be `{"createdAt":{"$gte":{"__type":"Date","iso":"2015-06-21T18:02:52.249Z"}}}`. For more information, see the description in the *Advanced Data Types* section of the [Data Storage REST API](/sdk/storage/guide/rest/).
-channels| Optional | Which channels to push to. Added to the where object as a condition.
-push_time| Optional | Sets the time at which the scheduled push is to be sent, which must be in UTC time and ISO8601 format, e.g. `2019-04-01T06:19:29.000Z`. Note that if the send time is less than 1 minute from the current time, the push will be sent immediately and will not follow the push_time parameter. If you need to implement periodic push, you can refer to [Implement Periodic Push Using Cloud Engine](#implement-periodic-push-using-cloud-engine).
-expiration_time| Optional | The absolute date and time when the message expires, in UTC time and ISO8601 format, for example, "2019-04-01T06:19:29.000Z". If the message is received by the client later than the message expiration time, the message is not displayed to the user.
-expiration_interval| Optional | The relative time in seconds for the message to expire. Counted from `push_time`, or from the time of the API call if `push_time` is not specified. It is recommended that `expiration_time` or `expiration_interval` be set for all pushes to avoid users getting a bunch of expired pushes after a long disconnect and reconnect.
-notification_id | Optional | Custom push id. Up to 16 characters long and can only be letters and numbers. If this parameter is not provided, we randomly assign a unique push id to each push request to distinguish between different pushes. The push id is used to count the number of target devices and messages reached, and is displayed in the push log in the console. Custom push ids can be used to combine multiple different requests under the same push id to get an overall count of the number of target devices and final message arrivals for a batch of push requests.
-req_id | Optional | Custom request id. Can be up to 16 characters long and can only contain letters and numbers. Different push requests with the same req_id within 5 minutes are considered duplicate requests and will be sent only once. The user can resend the request once with a unique req_id in the request to avoid missing failed push requests in case of an exception such as an interface timeout. And since the req_id is the same in both requests, we automatically filter duplicate push requests to ensure that each target end user receives at most one push message. Please note that **too many or too frequent retries can interfere with normal message delivery**.
-prod| Optional | ***Valid for iOS push only***. When sending iOS pushes using token authentication, this parameter is used to set whether to send the push to the development (***dev***) or production (***prod***) environment of the APNs. When sending iOS pushes using certificate authentication, this parameter is used to set whether to use a development certificate (***dev***) or a production certificate (***prod***). If `prod` is not specified and the HTTP header `X-LC-Prod` is passed without a value of 1, then it will be treated as `"prod": "dev"`, otherwise the default is `"prod": "prod"`. When using the certificate authentication method, we give priority to the certificate specified by deviceProfile if the device has deviceProfile set in the Installation record.
-topic | Optional | ***Only valid for iOS push with Token Authentication***. When using token authentication to send iOS push, you need to provide the APNs topic of the device for authentication. Normally, the iOS SDK automatically reads the bundle ID of the iOS application as the topic in the apnsTopic field of the Installation record, so this parameter is not required in the push request. However, it must be specified manually in the following cases: 1. using an iOS SDK lower than v4.2.0; 2. not using the iOS SDK (e.g. using React Native); 3. using a topic different from the iOS bundle ID for the target device.
-apns_team_id | Optional | ***Valid only for iOS push using Token Authentication***. If you are using Token Authentication to send iOS push, you need to provide the corresponding Team ID of the device for authentication. In general, if all APNs Topics are not duplicated under your Team ID, or if you have proactively set the apnsTeamId when saving the Installation, you do not need to provide this parameter; we will match the appropriate Team ID for each device to send the push. Otherwise, this parameter must be provided and you must ensure that the target devices of a single push request belong to the Team ID specified in this parameter through the where-query condition to ensure that the push is performed correctly.
-flow_control | Optional | Whether to enable smoothing. Disabled by default. The value represents the pushing speed, i.e. the number of target end users per second. The minimum value is 1000, and any value below the minimum is treated as the minimum.
-_notificationChannel | Optional | For Android 8.0 or higher, you need to pass the channel id to receive pushes properly. Please refer to the "Adapting for Android 8.0" section in the [Android Push Guide](/sdk/push/guide/android/).
-
-All properties in the `_Installation` table, whether built-in or custom, can be specified as query conditions by where, and all kinds of complex queries are supported.
-
-The objectId of a push is returned on success, and the returned objectId can be used to [query the push record](#query-push-record).
-
-```json
-{"objectId":"i4OcyCnyjckJOtzz","createdAt":"2021-11-23T08:05:54.921Z"}
-```
-
-If it fails, the server will return an error code and the reason for the error:
-
-```json
-{"code":107,"error":"Malformed json object. A json dictionary is expected."}
-```
-
-Some examples are given below. For more examples, see the "Queries" section of the [Data Storage REST API](/sdk/storage/guide/rest/).
-
-#### Push to All Devices
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{
- "data": {
- "alert": "Greetings!"
- }
- }' \
- https://{{host}}/1.1/push
-```
-
-#### Push to Android Devices
-
-```sh
-curl -X POST \
--H "X-LC-Id: {{appid}}" \
--H "X-LC-Key: {{masterkey}},master" \
--H "Content-Type: application/json" \
--d '{
- "where":{
- "deviceType": "android"
- },
- "data": {
- "alert": "Greetings!"
- }
- }' \
-https://{{host}}/1.1/push
-```
-
-#### Push to Devices on the public Channel
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{
- "channels":"public",
- "data": {
- "alert": "Greetings!"
- }
- }' \
- https://{{host}}/1.1/push
-```
-
-#### Push to Inactive Devices
-
-```sh
-curl -X POST \
--H "X-LC-Id: {{appid}}" \
--H "X-LC-Key: {{masterkey}},master" \
--H "Content-Type: application/json" \
--d '{
- "where":{
- "updatedAt":{
- "$lt":{"__type":"Date","iso":"2015-06-29T11:33:53.323Z"}
- }
- },
- "data": {
- "alert": "Greetings!"
- }
- }' \
-https://{{host}}/1.1/push
-```
-
-#### Push to Devices With Matching Custom Properties
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{
- "where": {
- "preOrder": true
- },
- "data": {
- "alert": "The sale is on!"
- }
- }' \
- https://{{host}}/1.1/push
-```
-
-The `where` queries are all about the properties in the `_Installation` table. This assumes that the table stores the boolean attribute `preOrder`.
-
-#### Push Based on Geographic Location
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{
- "where": {
- "owner": {
- "$inQuery": {
- "location": {
- "$nearSphere": {
- "__type": "GeoPoint",
- "latitude": 30.0,
- "longitude": -20.0
- },
- "$maxDistanceInMiles": 10.0
- }
- }
- }
- },
- "data": {
- "alert": "The highest temperature in Beijing tomorrow will be 40 degrees Celsius."
- }
- }' \
- https://{{host}}/1.1/push
-```
-
-The above example assumes that the installation has an owner attribute pointing to a record in the `_User` table, and the user has a `location` attribute of type GeoPoint, so we can do a push based on geographic location.
-
-### Push by Device ID List
-
-This interface is used to send pushes to a list of specified device IDs. The push process is faster and has lower latency than the query method because it does not query the Installation records of the target devices. For example, the following code is used to push a `Hello` message to the iOS devices with device token "device_token1", "device_token2", and "device_token3".
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{
- "data": {"alert" : "Hello"},
- "device_type": "ios",
- "device_ids": ["device_token1", "device_token2", "device_token3"]
- }' \
- https://{{host}}/1.1/push/devices
-```
-
-The parameters of this interface consist of two parts: push-channel-independent generic parameters and push-channel-specific channel parameters. The generic parameters are channel-independent, so you can use them for either iOS or Android devices.
-
-The supported generic parameters are:
-
-Name| Constraints | Description
----|--- | ---
-device_type | **Required** | Target device type. Can only be android or ios. You can only push to one device type at a time.
-device_ids | **Required** | The list of target device IDs. Up to 500 IDs. For iOS devices, the device ID is the deviceToken field in the _Installation table; for Android devices, the device ID is the installationId field in the _Installation table.
-data| **Required**| Same as [pushing by query criteria](#push-by-query-criteria).
-expiration_interval| Optional | Same as above.
-expiration_time| Optional | Same as above.
-notification_id | Optional | Same as above.
-req_id | Optional | Same as above.
-
-If the target devices are iOS devices, the following parameters can be included in addition to the generic parameters above:
-
-Name| Constraints | Description
----- | ---- | ----
-prod| Optional | Same as [pushing by query criteria](#push-by-query-criteria).
-topic | Optional | Same as above.
-apns_team_id | Optional | Same as above.
-device_profile | Optional | Specifies the custom iOS push certificate to use. This parameter is not required if you are using token authentication or if the push certificate is a configured "production environment certificate" or "development environment certificate". We will use the appropriate certificate based on the value of the `prod` parameter you provide.
-
-If the target devices are Android devices, the following parameters can be included in addition to the general parameters mentioned above:
-
-Name| Constraints | Description
----- | ---- | ----
-channel| Optional | Specifies the [Android Notification Channel][android-channel].
-
-[android-channel]: https://developer.android.com/develop/ui/views/notifications#ManageChannels
-
-### Expiration Time and Scheduled Push
-
-As mentioned above, you can specify the expiration time of a message using the `expiration_time` parameter:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{
- "expiration_time": "2015-10-07T00:51:13Z",
- "data": {
- "alert": "Your coupon expires on October 7th."
- }
- }' \
- https://{{host}}/1.1/push
-```
-
-`expiration_interval` can also be used to specify an expiration time, usually used with a scheduled push:
-
-```sh
-curl -X POST \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- -d '{
- "push_time": "2016-01-28T00:07:29.773Z",
- "expiration_interval": 86400,
- "data": {
- "alert": "This push was sent on January 28th at 8:07 BST and will expire in 24 hours (86400 seconds)"
- }
- }' \
- https://{{host}}/1.1/push
-```
-
-#### Retrieving and Canceling Scheduled Pushes
-
-Call the `POST /scheduledPushMessages` interface to retrieve scheduled push tasks that are currently waiting to be pushed. Calling this interface requires the **master key**:
-
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- https://{{host}}/1.1/scheduledPushMessages
-```
-
-The query will return a result similar to
-
-```json
-{
- "results": [
- {
- "id": 1,
- "expire_time": 1373912050838,
- "push_msg": {
- "through?": null,
- "app-id": "OLnulS0MaC7EEyAJ0uA7uKEF-gzGzoHsz",
- "where": {
- "sort": {
- "createdAt": 1
- },
- "query": {
- "installationId": "just-for-test",
- "valid": true
- }
- },
- "prod": "prod",
- "api-version": "1.1",
- "msg": {
- "message": "test msg"
- },
- "id": "XRs9jmWnLd0GH2EH",
- "notificationId": "mhWjvHvJARB6Q6ni"
- },
- "createdAt": "2016-01-21T00:47:46.000Z"
- }
- ]
-}
-```
-
-Here `push_msg` is the details of the push message and `expire_time` is the Unix timestamp of the time at which the message is set to be pushed.
-
-Based on the result of the query, you can cancel a scheduled push.
-Note that you must use the outermost id in the returned result.
-For example, to cancel the first scheduled push, use `results[0].id` instead of `results[0].push_msg.id`.
-For the above example, `1` should be used instead of `XRs9jmWnLd0GH2EH`:
-
-```sh
-curl -X DELETE \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{masterkey}},master" \
- -H "Content-Type: application/json" \
- https://{{host}}/1.1/scheduledPushMessages/1
-```
-
-#### Implement Periodic Push Using Cloud Engine
-
-You can use the scheduled task feature provided by Cloud Engine to implement periodic pushes.
-
-## Query Push Record
-
-The `/push` interface returns an `objectId` representing the push message after the push, and you can use this ID to call the following API to query the push record:
-
-```sh
-curl -X GET \
- -H "X-LC-Id: {{appid}}" \
- -H "X-LC-Key: {{appkey}}" \
- -H "Content-Type: application/json" \
- https://{{host}}/1.1/tables/Notifications/:objectId
-```
-
-The `:objectId` in the URL should be replaced with the objectId returned by the `/push` interface.
-
-The push record object will be returned. Refer to the section "Notification" in [Push Notification Overview](/sdk/push/guide/overview/) for the meaning of each field of the push record.
-
-## Viewing Push Status and Canceling Pushes
-
-During the process of sending a push, we will update the push status as the push task is executed in **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Push records**, where you can check the latest status of the push. For a description of the different push statuses, please refer to the Notification section of the [Push Notification Overview](/sdk/push/guide/overview/).
-
-Before the status of a push record reaches **done**, that is, before the push is complete, the "Cancel" button will appear next to the status information, and you can cancel the push by clicking it. The canceled push will be deleted from the push record.
-
-Note that canceling a push means canceling a push that is still in the queue and has not yet been sent. Similarly, pushes that have already been sent or stored in the offline cache cannot be canceled. Please do your best to test and confirm the content and query conditions of the target devices before sending pushes.
-
-## Restrictions
-
-* To avoid sending messages to a large number of users who are no longer active, we restrict push messages to devices with an `updatedAt` time in the `_Installation` table within the last three months. We will automatically exclude the devices that do not meet the criteria from the target devices after determining the target devices based on the push query criteria, and the excluded devices will not be counted in the target devices in **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Push records**.
-* To prevent performance issues due to large numbers of certificate errors, we limit the number of devices that can be pushed using **development certificates** to a maximum of 20,000 devices at a time. If more than 20,000 devices meet the push criteria, the system will reject the push and display "Error" in the **Status** column in **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Push records** with the message "dev profile disabled for massive push". Please be sure to set the push conditions appropriately when using the developer certificate.
-* Apple has a limit on the size of push messages, so please try to reduce the size of data to be sent for iOS push, otherwise it will be truncated. Please refer to the [APNs documentation](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) for details.
-
-If a push fails, you will see an error message in the **Status** section of **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Push records**.
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/unity.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/unity.mdx
deleted file mode 100644
index a62f7f873..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/push/guide/unity.mdx
+++ /dev/null
@@ -1,238 +0,0 @@
----
-title: Unity Push Guide
-sidebar_label: Unity Push
-sidebar_position: 5
-slug: /sdk/push/guide/unity/
----
-
-
-
-
-
-import {Conditional} from '/src/docComponents/conditional';
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-
-
-This article will show you how to use push notifications in your Unity project. We recommend that you take a look at [Push Notification Overview](/sdk/push/guide/overview/) if you haven’t already.
-
-Currently we only support iOS and some Android vendors (Huawei, Xiaomi, VIVO, OPPO, Meizu)FCM.
-
-## Getting Started
-
-### iOS
-
-Please first obtain an iOS push notification certificate by following [APNs Configuration Guide](/sdk/push/guide/ios-cert/).
-
-### Android
-
-Please first apply for push notification permissions from Android vendorsFCM by following [Android Mixpush Guide](/sdk/push/guide/android-mixpush/).
-
-Note that you only need to follow the guide to apply for push notification permissions from Android vendorsFCM. You **don’t** need to follow the guide to set up push notifications for Android.
-
-## Integrate the Push Notification Service
-
-### Install the SDK
-
-You can download the latest `unity-push.unitypackage` or `unity-push-without-gradle.unitypackage` from [SDK Releases](https://github.com/leancloud/csharp-sdk/releases).
-
-If your project has “no” other Android Gradle configurations, you can download `unity-push.unitypackage`. This package includes all iOS and Android configurations. You will only need to configure the parameters for different vendors.
-
-If your project “has” other Android Gradle configurations, you’ll need to download `unity-push-without-gradle.unitypackage`. This package doesn’t contain Android Gradle configurations related to push notifications. You will need to provide those configurations yourself.
-
-The SDK cannot be installed with UPM because it involves Android Gradle configurations.
-
-
-
-### Import the `LeanCloud-SDK-Realtime-Unity` Package:
-
-#### Method 1: Use Unity Package Manager
-
-Add the following dependency to the `Packages/manifest.json` file in your project:
-
-
-{
- `"dependencies":{
- "com.leancloud.realtime": "https://github.com/leancloud/csharp-sdk-upm.git#realtime-${sdkVersions.leancloud.csharp}"
- }`
-}
-
-
-You can view all installed packages by going to **Window > Package Manager**.
-
-#### Method 2: Import Manually
-
-1. Locate the **LeanCloud C# SDK** download URL on [Downloads](/tap-download) and download `LeanCloud-SDK-Realtime-Unity.zip`.
-
-2. Unzip `LeanCloud-SDK-Realtime-Unity.zip` and drag the Plugins directory to Unity.
-
-
-
-### Set Up
-
-#### iOS
-
-Provide the iOS developer’s TeamId during initialization. See [Initialization](#initialization).
-
-#### Android
-
-
-
-##### Huawei
-
-Download the file named `agconnect-services.json` applied from Huawei Push Service and place it in `Assets/LeanCloud/Push/Android/HuaWei/hms/`. When bundling, this file will be copied to the directory specified by Huawei.
-
-##### VIVO
-
-Provide the `app_id` and `api_key` applied from VIVO Push Service as the values of `com.vivo.push.app_id` and `com.vivo.push.api_key` under `meta-data` in `Assets/Plugins/Android/AndroidMenifest.xml`.
-
-##### Other Platforms
-
-For Xiaomi, OPPO, and Meizu, provide the configurations when initializing the SDK.
-
-Note that `${applicationId}` is used for the package name in `AndroidMenifest.xml`. If you need different `applicationId`s for different channels, please edit it manually.
-
-### Initialization
-
-Here you need to initialize the SDK for different vendors according to the platform and device information.
-You can use Xiaomi Push Service as the default option as it is able to establish connections when used under other vendors.
-
-```cs
-using LeanCloud.Storage;
-using LeanCloud;
-using LeanCloud.Push;
-using System.Threading.Tasks;
-using LC.Newtonsoft.Json;
-
-LCApplication.Initialize("{{appid}}", "{{appkey}}", "https://please-replace-with-your-customized.domain.com");
-
-if (Application.platform == RuntimePlatform.IPhonePlayer) {
- LCIOSPushManager.RegisterIOSPush(IOS_TEAM_ID);
-} else if (Application.platform == RuntimePlatform.Android) {
- string deviceModel = SystemInfo.deviceModel.ToLower();
- if (deviceModel.Contains("huawei")) {
- LCHuaWeiPushManager.RegisterHuaWeiPush();
- } else if (deviceModel.Contains("oppo")) {
- LCOPPOPushManager.RegisterOPPOPush(OPPO_APP_KEY, OPPO_APP_SECRET);
- } else if (deviceModel.Contains("vivo")) {
- LCVIVOPushManager.RegisterVIVOPush();
- } else if (deviceModel.Contains("meizu")) {
- LCMeiZuPushManager.RegisterMeiZuPush(MEIZU_APP_ID, MEIZU_APP_KEY);
- } else /*if (deviceModel.Contains("xiaomi"))*/ {
- // Try to register Xiaomi Push Service for other vendors
- LCXiaoMiPushManager.RegisterXiaoMiPush(XIAOMI_APP_ID, XIAOMI_APP_KEY);
- }
-}
-```
-
-
-
-
-
-To initialize FCM:
-
-```cs
-LCFCMPushManager.RegisterFCMPush();
-```
-
-
-
-## Installation
-
-The SDK comes with Installation objects that can be used to hold tokens and other data needed for push notifications.
-
-```cs
-LCInstallation lcInstallation = await LCInstallation.GetCurrent();
-```
-
-## Send Push Notifications
-
-By default, **Prevent clients from sending push notifications** is checked in **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Settings** so clients won’t be able to send push notifications to other devices without restriction.
-We recommend leaving this option enabled and sending push notifications through the REST API or the dashboard.
-
-If you need to send push notifications from clients, uncheck this option.
-
-### Send Push Notifications to All Devices
-
-```csharp
-try {
- LCPush push = new LCPush {
- Data = new Dictionary {
- { "alert", pushData }
- }
- };
- await push.Send();
-} catch (Exception e) {
- Debug.LogError(e);
-}
-```
-
-### Send Push Notifications to Specific Users
-
-Below is an example of how to send a push notification from a client to an iOS device in the test environment:
-
-```cs
-try {
- LCPush push = new LCPush {
- Data = new Dictionary {
- { "alert", pushData }
- },
- IOSEnvironment = LCPush.IOSEnvironmentDev,
- };
- LCInstallation installation = await LCInstallation.GetCurrent();
- push.Query.WhereEqualTo("objectId", installation.ObjectId);
- await push.Send();
-} catch (Exception e) {
- Debug.LogError(e);
-}
-```
-
-## Respond to Push Notifications
-
-### Message Format
-
-For more information about the message format, see [Push Notification REST API Guide](/sdk/push/guide/rest#推送消息). For Android devices, the default message content parameter contains the following properties:
-
-```json
-{
- "alert": "Message content",
- "title": "The title that appears in the notification center",
- "custom-key": "Custom properties; custom-key is just an example and you can use any other keys"
-}
-```
-
-### Perform an Action When a User Taps a Push Notification
-
-When a user taps a push notification to open your app, the Unity scene might not be initialized. This means that Unity may not know which notification was tapped.
-
-To provide the **notification parameters** to Unity, the SDK will cache the parameters in the native layer when sending push notifications. It provides a C# interface for your program to retrieve the parameters:
-
-```cs
-Dictionary launchData = await LCPushBridge.Instance.GetLaunchData();
-```
-
-## Verification
-
-Once you have initialized the SDK in your project and run your project on a real iOS or Android device, an entry with the device information will be created in the `_Installation` table.
-
-You can send a test push notification with custom conditions on **Developer Center > Your game > Game Services > Cloud Services > Push Notification > Send notifications**. This will help you verify that your device can receive push notifications.
-You can send the push notification by providing the objectId of the Installation. For iOS you can also provide the deviceToken and for Android you can use the registrationId.
-
-## FAQ
-### How do I remove the push notification services provided by certain vendors?
-
-
-
-You might want your app to support only certain vendors, or to release different packages for different channels. In this case, you can remove the vendor SDKs for unused vendors to reduce the size of the packages:
-
-
-
-
-
-The SDK includes vendor SDKs for some Chinese Android vendors. You can remove them to reduce the size of the package.
-
-
-
-- Delete `Assets/LeanCloud/Push/Android/xx`. Here `xx` is the vendor name such as `HuaWei` and `XiaoMi`.
-- Delete `dependencies` from `Assets/Plugins/Android/mainTemplate.gradle`.
-- Delete vendor SDKs from `Assets/Plugins/Android/AndroidManifest.xml` (see to the comments in the file for more information).
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/rtc/_category_.json b/i18n/en/docusaurus-plugin-content-docs/current/shadow/rtc/_category_.json
deleted file mode 100644
index 576865718..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/rtc/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "实时语音",
- "collapsed": true,
- "position": 17
-}
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/rtc/features.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/rtc/features.mdx
deleted file mode 100644
index e24b97b04..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/rtc/features.mdx
+++ /dev/null
@@ -1,76 +0,0 @@
----
-title: RTC Introduction
-sidebar_label: Introduction
-sidebar_position: 1
-slug: /sdk/rtc/features/
----
-
-
-
-
-
-
-RTC is a one-stop voice chat solution that provides real-time voice chat and voice compliance services for a variety of game types such as FPS, MOBA, MMORPG, casual matchmaking, online table games, etc. All your voice chat needs can be met with one easy integration.
-
-## Key Benefits
-
-- Easy integration: RTC works with popular frameworks. With a simple integration, all your voice chat requirements can be met.
-- Low latency, high quality voice: RTC supports two-player or multi-player real-time audio call with low latency and high quality, which can perform well in weak network environment.
-- Compliance for English and Chinese content: RTC identifies all types of illegal content, intelligently identifying pornographic, violent, abusive, advertising, and other types of sensitive or unwanted information in real-time voice and audio files.
-- Global service availability: RTC comes with massive acceleration nodes deployed globally, covering major countries and regions around the world, enabling players to access nearby servers and providing real-time voice services with low latency and no lag.
-
-## Features
-
-### Real-Time Voice Conversation
-
-RTC allows up to 6 people to talk at the same time and transmits data with ultra-low latency. It is ideal for multiplayer team hacking and other competitive gaming scenarios. RTC also supports:
-
-- Blocking the voice of others in the room
-- Event callbacks for members who enter the room and speak, which the game can use to determine player status
-- Enabling/disabling loopback
-
-### Voice Compliance
-
-RTC supports filtering of illegal content in English and Chinese. The game can report the voice to the compliance module in different slice lengths. If a violation is detected, the game will receive a callback, and the game can decide whether to kick the player out of the room, ban them, or take no action at all.
-Note: Currently, language compliance only supports Mandarin and a few dialects. Other languages will be supported in future versions.
-
-### 3D Voice
-
-3D Voice can convert the sound without orientation into the sound with orientation, which gives the player the feeling that the sound is coming from one place in the room, suitable for creating an immersive listening experience for battle royale, FPS, and other games.
-
-:::info
-
-3D Voice is still in beta. If you encounter any problems or have any suggestions during the trial, please feel free to contact us via tickets.
-
-:::
-
-### Developer Center
-
-The Developer Center provides you with the following basic features:
-
-- Enable and configure the RTC service
-- Remotely turn on/off the RTC
-- Query the number of active users, maximum number of simultaneous online users, player voice hours, and other real-time voice data
-- View real-time voice usage and billing details
-
-## Integrate the Service
-
-### Getting Ready
-
-1. Become a TapTap developer;
-
-2. Create a game in the TapTap Developer Center;
-
-3. Go to "Game Services"-"RTC" and enable the service;
-
-4. Configure the compliance service
-
- ![](https://capacity-files.lcfile.com/f7lyeyQrmn9YIwIBBlWq7HIVTqLdndQA/rtc-console.png)
-
- 1. Set the voice slice length: A shorter slice length sacrifices some of the contextual semantics for a relatively fast callback speed. You can choose the slice length according to your actual scenario;
- 2. Set the callback address: The callback address for receiving speech compliance recognition results;
-
-5. Download the TapSDK (minimum supported version is v3.5.0) and integrate it into the game package;
-
-6. Test the game package.
-
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/shadow/rtc/guide.mdx b/i18n/en/docusaurus-plugin-content-docs/current/shadow/rtc/guide.mdx
deleted file mode 100644
index 786140715..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/shadow/rtc/guide.mdx
+++ /dev/null
@@ -1,1491 +0,0 @@
----
-title: RTC Guide
-sidebar_label: Guide
-sidebar_position: 2
-slug: /sdk/rtc/guide/
----
-
-
-
-
-
-import MultiLang from '/src/docComponents/MultiLang';
-import CodeBlock from '@theme/CodeBlock';
-import sdkVersions from '/src/docComponents/sdkVersions';
-import Mermaid from '/src/docComponents/Mermaid';
-import {Conditional} from '/src/docComponents/conditional';
-
-
-## Install the SDK
-
-Download the SDK from the [Downloads](/tap-download) page and import the `TapRTC` module:
-
-
-
-<>
-
-Make sure [git-lfs] is installed on your system, then add the dependencies via UPM:
-
-[git-lfs]: https://git-lfs.github.com
-
-
-{`"dependencies":{
- ...
- "com.taptap.tds.rtc":"https://github.com/TapTap/TapRTC-Unity.git#${sdkVersions.taptap.unity}",
-}`}
-
->
-
-<>
-
-{`repositories{
- flatDir {
- dirs 'libs'
- }
-}
-dependencies {
- ...
- implementation (name:'TapRTC_${sdkVersions.taptap.rtc}', ext:'aar')
-}`}
-
->
-
-<>
-
-1. Select the project in Xcode, then go to Build Settings > Other Linker Flags, add `-ObjC` and `-Wl -ld_classic`.
-2. Drag and drop the `TapRTC_SDK` directory into the project directory.
-3. Drag the `TapRTC.framework` and `GMESDK.framework` files into the project and select `Do Not Embed`.
-4. Drag the TapRTC.bundle resource file into the project.
-5. Add the system libraries that the SDK depends on: libz, libresolv, libiconv, libc++, CoreMedia.framework, CoreAudio.framework, AVFoundation.framework, SystemConfiguration.framework, UIKit.framework, AudioToolbox.framework, OpenAL.framework, Security.framework.
-
-
-TapRTC.framework
-
-
->
-
-
-
-## Caution
-
-* RTC must be configured in the Developer Center before use.
-* You need to implement [the appropriate signature authentication service](#server-side-authentication) on your own servers.
-* C# SDK must periodically call the `TapRTC.Poll` interface to trigger relevant event callbacks.
-
-On Android, you need to apply for network and audio-related permissions:
-
-```xml
-
-
-
-
-
-
-
-```
-
-For iOS, you need to request microphone permission (`Privacy - Microphone Usage Description`).
-
-Games usually have no need for background calls; players usually play the game while the voice is playing, and the game stays in the foreground.
-If the type of game is special and needs to support background calls, then you must also request background play permission:
-Configure **Capability > Background Modes > Audio, AirPlay, and Picture in Picture** in target.
-
-## Core Interfaces
-
-The RTC is initialized and configured with `TapRTCConfig`. The initialization process is asynchronous and you must wait for the initialization result to be returned before proceeding to the next step.
-
-* For `ClientId`, `ClientToken`, and `ServerUrl`, see the note about [application credentials](/sdk/storage/guide/setup-dotnet/#credentials).
-* `UserId`: Developer-defined user ID for tagging players.
-* `DeviceId`: Developer-defined device ID used to tag the device.
-* `AudioPerfProfile`: Audio quality profile (`LOW`, `MID`, or `HIGH`; default is `MID`).
-
-
-
-```cs
-using TapTap.RTC;
-
-var config = new TapRTCConfig.Builder()
- .ClientID("ClientId")
- .ClientToken("ClientToken")
- .ServerUrl("ServerUrl")
- .UserId("UserId")
- .DeviceId("DeviceId")
- .AudioProfile(AudioPerfProfile.MID)
- .ConfigBuilder();
-
-ResultCode code = await TapRTC.Init(config);
-
-if (code == ResultCode.OK) {
- // Initialized successfully
-} else {
- // Failed
-}
-// In the RTC module of the SDK, interfaces that return ResultCode indicate success with ResultCode.OK.
-```
-
-```java
-import android.app.Application;
-import com.taptap.taprtc.Config;
-import com.taptap.taprtc.Config.AudioPerfProfile;
-import com.taptap.taprtc.DeviceID;
-import com.taptap.taprtc.UserID;
-import com.taptap.taprtc.TapRTCEngine;
-
-public class MyApp extends Application {
- @Override
- public void onCreate() {
- super.onCreate();
-
- Config config = new Config();
- config.appId = "AppId";
- config.appKey = "AppKey";
- config.serverUrl = "ServerUrl"; // Please remove the http/https prefix and specify a custom domain name or a domain name in the form of "xxx.cloud.tds1.tapapis.cn" or something similar.
- config.userId = new UserID("UserId");
- config.deviceId = new DeviceID("DeviceId");
- // This must be set to LOW if you want to use the Range Audio feature
- config.profile = AudioPerfProfile.MID;
- try {
- TapRTCEngine.get().init(this, config, resultCode -> {
- if (resultCode == ResultCode.OK) {
- // Initialized successfully
- } else {
- // Failed
- }
- });
- } catch (TapRTCException e) {
- throw new RuntimeException(e);
- }
- }
-}
-// In the RTC module of the SDK, interfaces that return ResultCode indicate success with ResultCode.OK.
-```
-
-```objc
-TapRTCConfig *config = [[TapRTCConfig alloc] initWithAppId:@"AppId"
-appKey:@"AppKey" serverUrl:@"ServerUrl"
-userId:@"UserId" deviceId:@"DeviceId"
-profile:AudioPerfProfileMID];
-TapRTCEngine *engine = [TapRTCEngine defaultEngine];
-[engine initializeWithConfig:config resultBlock:^(NSError * _Nullable error) {
- if (error) {
- // handle error
- }
-})];
-```
-
-
-
-### Trigger Callback Events
-
-For the C# SDK, you must call the `Poll` method in the `Update` method to trigger the event callback. Failure to call this method will cause the SDK to throw exceptions.
-
-```cs
-public void Update()
-{
- ResultCode code = TapRTC.Poll();
- if (code == ResultCode.OK) {
- // Triggered callback successfully
- } else {
- // Failed
- }
-}
-```
-
-For the Java SDK and the Objective-C SDK, you do not need to call the `Poll` method regularly.
-
-### Resume
-
-
-
-```cs
-ResultCode code = TapRTC.Resume();
-if (code == ResultCode.OK) {
- // Resumed
-} else {
- // Failed
-}
-```
-
-```java
-import com.taptap.taprtc.TapRTCEngine;
-
-ResultCode code = TapRTCEngine.get().resume();
-```
-
-```objc
-TapRTCResultCode resultCode = [engine resume];
-if (resultCode == TapRTCResultCode_Success) {
- // resumed
-} else {
- // failed to resume
-}
-```
-
-
-
-### Pause
-
-
-
-```cs
-ResultCode code = TapRTC.Pause();
-if (code == ResultCode.OK) {
- // Paused
-} else {
- // Failed
-}
-```
-
-```java
-import com.taptap.taprtc.TapRTCEngine;
-
-ResultCode code = TapRTCEngine.get().pause();
-```
-
-```objc
-TapRTCResultCode resultCode = [engine pause];
-if (resultCode == TapRTCResultCode_Success) {
- // paused
-} else {
- // failed to pause
-}
-```
-
-
-
-## Room-Related Interfaces
-
-### Create Rooms
-
-After successful initialization, the SDK can make live voice calls only after creating a room.
-The room number (`roomId`) must be specified when creating the room.
-[Whether to enable range audio](#range-audio) must also be set when creating the room. The C# SDK does not enable it by default. For the Java SDK, you must specify whether to enable range audio when creating the room. The Objective-C SDK uses a separate interface to create rooms with range audio.
-
-
-
-```cs
-bool enableRangeAudio = false;
-var room = await TapRTC.AcquireRoom("roomId", enableRangeAudio);
-```
-
-```java
-import com.taptap.taprtc.TapRTCEngine;
-import com.taptap.taprtc.RoomID;
-
-RoomId roomId = new RoomID("roomId");
-boolean enableRangeAudio = false;
-TapRTCRoom room = TapRTCEngine.get().acquireRoom(roomId, enableRangeAudio);
-```
-
-```objc
-TapRTCRoom *room = [engine acquireRoomWithRoomId:@"roomID"];
-```
-
-
-
-### Register Room-Related Callback Events
-
-
-
-```cs
-room.RegisterEventAction(new TapRTCEvent()
-{
- OnDisconnect = (code, message) => { label.text += "\n" + $"Disconnected code:{code} msg:{e}"; },
- OnEnterFailure = s => { label.text += "\n" + $"Failed to enter the room:{s}"; },
- OnEnterSuccess = () => { label.text += "\n" + $"Entered the room"; },
- OnExit = () => { label.text += "\n" + $"Left the room"; },
- OnUserEnter = userId => { label.text += "\n" + $"{userId} entered the room"; },
- OnUserExit = userId => { label.text += "\n" + $"{userId} left the room"; },
- OnUserSpeaker = (userId, volume) => { label.text += "\n" + $"{userId} is speaking in the room; the volume is {volume}"; },
- OnUserSpeakEnd = userId => { label.text += "\n" + $"{userId} stopped speaking"; },
- // Returns the audio quality after the switch; see the section "Switch Audio Quality" below
- OnRoomTypeChanged = (i) => { label.text += "\n" + $"The audio quality is now {i}"; },
- OnRoomQualityChanged = (weight, loss, delay) =>
- {
- Debug.Log($"Audio quality:{weight} Packet loss:{loss}% Delay:{delay}ms");
- },
-
-});
-```
-
-```java
-import com.taptap.taprtc.UserID;
-import com.taptap.taprtc.TapRTCRoom;
-
-room.registerCallback(new TapRTCRoom.Callback() {
- // Entered the room
- @Override public void onEnterSuccess() {}
-
- // Failed to enter the room
- @Override public void onEnterFailure(String msg) {}
-
- // Lost connection
- @Override public void onDisconnect() {}
-
- // Reconnected
- @Override public void onReConnected() {}
-
- // Current player left the room
- @Override public void onExit() {}
-
- // A player has entered the room
- @Override public void onUserEnter(UserID userId) {}
-
- // A player has left the room
- @Override public void onUserExit(UserID userId) {}
-
- // A player started talking
- @Override public void onUserSpeakStart(UserID userId, int volume) {}
-
- // A player stopped talking
- @Override public void onUserSpeakEnd(UserID userId) {}
-
- // Audio quality changed
- @Override public void onRoomQualityChanged(int weight, double loss, int delay) {}
-});
-```
-
-```objc
-// Need to implement TapRTCRoomDelegate
-
-// Entered the room
-- (void)onEnterSuccess;
-
-// Failed to enter the room
-- (void)onEnterFailure:(NSError *)error;
-
-// A player has entered the room
-- (void)onUsersEnter:(NSString *)userId;
-
-// A player has left the room
-- (void)onUsersExit:(NSString *)userId;
-
-// Current player left the room
-- (void)onExit;
-
-// Lost connection
-- (void)onDisconnect;
-
-// Reconnected
-- (void)onReconnected;
-
-// player started talking
-- (void)onUsersSpeakStart:(NSString *)userId volume:(NSInteger)volume;
-
-// A player stopped talking
-- (void)onUsersSpeakEnd:(NSString *)userId;
-
-// Audio quality changed (returns audio quality, packet loss, and delay)
-- (void)onQualityCallBackWithWeight:(int)weight loss:(float)loss delay:(int)delay;
-```
-
-
-
-### Join a Room
-
-Enter a room using the [server-side generated authentication information](#server-side-authentication).
-
-After successfully entering a room, a callback is made via `OnEnterSuccess` in `TapRTCEvent`.
-
-
-
-```cs
-ResultCode code = await room.Join("authBuffer");
-if (code == ResultCode.OK) {
- // Successfully joined the room
-}
-if (code == ResultCode.ERROR_ALREADY_IN_ROOM) {
- // The player is already in the room
-}
-```
-
-```java
-import com.taptap.taprtc.Authority;
-
-Authority authBuffer = new Authority("authBuffer");
-ResultCode code = room.join(authBuffer);
-if (code == ResultCode.OK) {
- // Successfully joined the room
-}
-if (code == ResultCode.ERROR_ALREADY_IN_ROOM) {
- // The player is already in the room
-}
-```
-
-```objc
-[room joinWithAuth:@"authBuffer"];
-```
-
-
-
-The `authBuffer' is the authentication information generated on the server side, as described in the [Server-Side Authentication](#server-side-authentication) section below.
-
-### Exit a Room
-
-After leaving a room, a callback is made via `OnExit` in `TapRTCEvent`.
-
-
-
-```cs
-ResultCode code = room.Exit();
-```
-
-```java
-ResultCode code = room.exit();
-```
-
-```objc
-TapRTCResultCode resultCode = [room exit];
-if (resultCode == TapRTCResultCode_Success) {
- // exited
-} else {
- // failed exit
-}
-```
-
-
-
-### Listen to Someone's Voice (Enabled by Default)
-
-
-
-```cs
-ResultCode code = room.EnableUserAudio("userId");
-if (code == ResultCode.OK) {
- // Success
-}
-if (code == ResultCode.ERROR_USER_NOT_EXIST) {
- // The player does not exist
-}
-```
-
-```java
-import com.taptap.taprtc.UserID;
-
-UserId userId = new UserID("userId");
-ResultCode code = room.enableUserAudio(userId);
-if (code == ResultCode.OK) {
- // Success
-}
-if (code == ResultCode.ERROR_USER_NOT_EXIST) {
- // The player does not exist
-}
-```
-
-```objc
-TapRTCResultCode resultCode = [room enableUserAudioWithUserId:@"userId"];
-```
-
-
-
-### Disable Someone's Voice
-
-
-
-```cs
-ResultCode code = room.DisableUserAudio("userId");
-if (code == ResultCode.OK) {
- // Success
-}
-if (code == ResultCode.ERROR_USER_NOT_EXIST) {
- // The player does not exist
-}
-```
-
-```java
-import com.taptap.taprtc.UserID;
-
-UserId userId = new UserID("userId");
-ResultCode code = room.disableUserAudio(userId);
-if (code == ResultCode.OK) {
- // Success
-}
-if (code == ResultCode.ERROR_USER_NOT_EXIST) {
- // The player does not exist
-}
-```
-
-```objc
-TapRTCResultCode resultCode = [room disableUserAudioWithUserId:@"userId"];
-```
-
-
-
-### Enable/Disable Voice
-
-This interface sets whether or not to receive audio.
-In general, it is recommended that games use the [interface to turn on/off speakers](#enabledisable-speaker).
-
-
-
-```cs
-// Enable
-ResultCode code = room.EnableAudioReceiver(true);
-
-// Disable
-ResultCode code = room.EnableAudioReceiver(false);
-```
-
-```java
-// Enable
-ResultCode code = room.enableAudioReceiver(true);
-
-// Disable
-ResultCode code = room.enableAudioReceiver(false);
-```
-
-```objc
-TapRTCResultCode resultCode = [room enableAudioReceiver:YES];
-```
-
-
-
-### Switch Audio Quality
-
-There are three levels of audio quality: LOW, MID, and HIGH.
-
-You can change the audio quality after you enter the room.
-
-
-
-```cs
-room.ChangeRoomType(AudioPerfProfile.LOW);
-room.ChangeRoomType(AudioPerfProfile.MID);
-room.ChangeRoomType(AudioPerfProfile.HIGH);
-```
-
-```java
-// Not yet supported
-```
-
-```objc
-// Not yet supported
-```
-
-
-
-Changing the audio quality triggers the `OnRoomTypeChanged` callback.
-
-### Get the Users in the Room
-
-
-
-```cs
-HashSet userIdList = room.Users;
-```
-
-```java
-List userIdList = room.getUsers();
-```
-
-```objc
-[room getUsers:^(NSArray*userIDs, NSError * _Nullable error) {
- if (error) {
- // Handle the error
- } else {
- // userIDs is the IDs of the users in the room
- }
-})];
-```
-
-
-
-## Audio-Related Interfaces
-
-### Enable/Disable Microphone
-
-
-
-```cs
-// Enable
-ResultCode code = TapRTC.GetAudioDevice().EnableMic(true);
-
-// Disable
-ResultCode code = TapRTC.GetAudioDevice().EnableMic(false);
-```
-
-```java
-// Enable
-boolean ok = TapRTCEngine.get().getAudioDevice().enableMic(true);
-
-// Disable
-boolean ok = TapRTCEngine.get().getAudioDevice().enableMic(false);
-```
-
-```objc
-// Enable
-TapRTCResultCode code = [engine.audioDevice enableMic:YES];
-
-// Disable
-TapRTCResultCode code = [engine.audioDevice enableMic:NO];
-```
-
-
-
-### Enable/Disable Speaker
-
-
-
-```cs
-// Enable
-ResultCode code = TapRTC.GetAudioDevice().EnableSpeaker(true);
-
-// Disable
-ResultCode code = TapRTC.GetAudioDevice().EnableSpeaker(false);
-```
-
-```java
-// Enable
-boolean ok = TapRTCEngine.get().getAudioDevice().enableSpeaker(true);
-
-// Disable
-boolean ok = TapRTCEngine.get().getAudioDevice().enableSpeaker(false);
-```
-
-```objc
-// Enable
-TapRTCResultCode code = [engine.audioDevice enableSpeaker:YES];
-
-// Disable
-TapRTCResultCode code = [engine.audioDevice enableSpeaker:NO];
-```
-
-
-
-### Set/Get Volume
-
-Volume is an integer from 0 to 100.
-
-
-
-```cs
-int vol = 60;
-
-// Set microphone volume
-ResultCode code = TapRTC.GetAudioDevice().SetMicVolume(vol);
-// Set speaker volume
-ResultCode code = TapRTC.GetAudioDevice().SetSpeakerVolume(vol);
-
-// Get microphone volume
-int micVolume = TapRTC.GetAudioDevice().GetMicVolume();
-// Get speaker volume
-int speakerVolume = TapRTC.GetAudioDevice().GetSpeakerVolume();
-```
-
-```java
-int vol = 60;
-
-// Set microphone volume
-TapRTCEngine.get().getAudioDevice().setMicVolume(vol);
-// Set speaker volume
-boolean ok = TapRTCEngine.get().getAudioDevice().setSpeakerVolume(vol);
-
-// Get microphone volume
-int micVolume = TapRTCEngine.get().getAudioDevice().getMicVolume();
-// Get speaker volume
-int speakerVolume = TapRTCEngine.get().getAudioDevice().getSpeakerVolume();
-```
-
-```objc
-int vol = 60;
-
-// Set microphone volume
-TapRTCResultCode code = [engine.audioDevice setMicVolume:vol];
-// Set speaker volume
-TapRTCResultCode code = [engine.audioDevice setSpeakerVolume:vol];
-
-// Get microphone volume
-int micVolume = [engine.audioDevice getMicVolume];
-// Get speaker volume
-int speakerVolume = [engine.audioDevice getSpeakerVolume];
-```
-
-
-
-### Enable/Disable Audio Play
-
-
-
-```cs
-// Enable
-ResultCode code = TapRTC.GetAudioDevice().EnableAudioPlay(true);
-// Disable
-ResultCode code = TapRTC.GetAudioDevice().EnableAudioPlay(false);
-```
-
-```java
-// Enable
-boolean ok = TapRTCEngine.get().getAudioDevice().enableAudioPlay(true);
-// Disable
-boolean ok = TapRTCEngine.get().getAudioDevice().enableAudioPlay(false);
-```
-
-```objc
-// Enable
-TapRTCResultCode code = [engine.audioDevice enableAudioPlay:YES];
-
-// Disable
-TapRTCResultCode code = [engine.audioDevice enableAudioPlay:NO];
-```
-
-
-
-### Enable/Disable Loopback
-
-
-
-```cs
-// Enable
-ResultCode code = TapRTC.GetAudioDevice().EnableLoopback(true);
-// Disable
-ResultCode code = TapRTC.GetAudioDevice().EnableLoopback(false);
-```
-
-```java
-// Enable
-boolean ok = TapRTCEngine.get().getAudioDevice().enableLoopback(true);
-// Disable
-boolean ok = TapRTCEngine.get().getAudioDevice().enableLoopback(false);
-```
-
-```objc
-// Enable
-TapRTCResultCode code = [engine.audioDevice enableLoopback:YES];
-
-// Disable
-TapRTCResultCode code = [engine.audioDevice enableLoopback:NO];
-```
-
-
-
-## Range Audio
-
-The range audio feature can support the following functions:
-
-- Allowing other team members within a certain range of the player to hear the player's voice;
-- Support for a large number of users to turn on the microphone at the same time for voice calls in the same room.
-
-To use range audio, you must first specify that range audio is enabled when you create a room:
-
-
-
-```cs
-bool enableRangeAudio = true;
-var room = await TapRTC.AcquireRoom("roomId", enableRangeAudio);
-```
-
-```java
-import com.taptap.taprtc.TapRTCEngine;
-import com.taptap.taprtc.RoomID;
-
-RoomId roomId = new RoomID("roomId");
-boolean enableRangeAudio = true;
-TapRTCRoom room = TapRTCEngine.get().acquireRoom(roomId, enableRangeAudio);
-```
-
-```objc
-TapRTCRoom *room = [engine acquireRangeAudioRoomWithRoomId:@"roomID"];
-```
-
-
-
-Also, before players enter the room, they must **change the audio quality to LOW**:
-
-
-
-```cs
-room.ChangeRoomType(AudioPerfProfile.LOW);
-```
-
-```java
-// The Java SDK does not provide an interface to switch the audio quality.
-// With the Java SDK, if you want to use the range audio feature, **set the audio quality to LOW when initializing the SDK**.
-```
-
-```objc
-// The Objective-C SDK automatically sets the audio quality to LOW when you enter a room with range audio
-```
-
-
-
-Next, set the team number and voice mode:
-
-- World Mode: Other team members within [a certain range] (#set-audio-reception-range) of the current player can hear the player's voice;
-- Team Mode: Only team members can talk to each other.
-
-In both modes, team members can talk to each other regardless of distance.
-
-
-
-```cs
-int teamId = 12345678;
-ResultCode code = room.GetRtcRangeAudioCtrl().SetRangeAudioTeam(teamId);
-if (code == ResultCode.OK) {
- // Success
-}
-if (code == ResultCode.ERROR_NOT_RANGE_ROOM) {
- // Range audio is not enabled for this room
-}
-
-// World Mode
-ResultCode resultCode = room.GetRtcRangeAudioCtrl().SetRangeAudioMode(RangeAudioMode.WORLD);
-if (resultCode == ResultCode.OK) {
- // Success
-}
-if (resultCode == ResultCode.ERROR_NOT_RANGE_ROOM) {
- // Range audio is not enabled for this room
-}
-// Team Mode
-ResultCode resultCode = room.GetRtcRangeAudioCtrl().SetRangeAudioMode(RangeAudioMode.TEAM);
-if (resultCode == ResultCode.OK) {
- // Success
-}
-if (resultCode == ResultCode.ERROR_NOT_RANGE_ROOM) {
- // Range audio is not enabled for this room
-}
-```
-
-```java
-import com.taptap.taprtc.TapRTCRangeAudioCtrl.RangeAudioMode;
-
-int teamId = 12345678;
-ResultCode code = room.rangeAudioCtrl().setRangeAudioTeam(new TeamID(teamId));
-if (code == ResultCode.OK) {
- // Success
-}
-if (code == ResultCode.ERROR_NOT_RANGE_ROOM) {
- // Range audio is not enabled for this room
-}
-
-// World Mode
-ResultCode resultCode = room.rangeAudioCtrl().setRangeAudioMode(RangeAudioMode.WORLD);
-if (resultCode == ResultCode.OK) {
- // Success
-}
-if (resultCode == ResultCode.ERROR_NOT_RANGE_ROOM) {
- // Range audio is not enabled for this room
-}
-
-// Team Mode
-ResultCode resultCode = room.rangeAudioCtrl().setRangeAudioMode(RangeAudioMode.TEAM);
-if (resultCode == ResultCode.OK) {
- // Success
-}
-if (resultCode == ResultCode.ERROR_NOT_RANGE_ROOM) {
- // Range audio is not enabled for this room
-}
-```
-
-```objc
-int teamId = 12345678;
-TapRTCResultCode resultCode = [room setRangeAudioTeamId:teamId];
-
-// World Mode
-TapRTCResultCode code = [room setRangeAudioMode:TapRTCRangeAudioModeWorld];
-TapRTCResultCode code = [room setRangeAudioMode:TapRTCRangeAudioModeTeam];
-```
-
-
-
-Then enter the room and [Set Audio Reception Range](#set-audio-reception-range) and [Update Source Orientation](#update-source-orientation) to make the range audio effective.
-
-If you want to change the voice mode after entering the room, you can call `SetRangeAudioMode` again.
-
-### Set Audio Reception Range
-
-The audio reception range controls whether or not other team members can hear your voice in world mode, and is invoked after you enter the room and usually only needs to be set once.
-
-
-
-```cs
-int range = 300;
-ResultCode code = room.GetRtcRangeAudioCtrl().UpdateAudioReceiverRange(range);
-if (code == ResultCode.OK) {
- // Success
-}
-if (code == ResultCode.ERROR_NOT_RANGE_ROOM) {
- // Range audio is not enabled for this room
-}
-```
-
-```java
-int range = 300;
-ResultCode code = room.rangeAudioCtrl().updateAudioReceiverRange(range);
-if (code == ResultCode.OK) {
- // Success
-}
-if (code == ResultCode.ERROR_NOT_RANGE_ROOM) {
- // Range audio is not enabled for this room
-}
-```
-
-```objc
-int range = 300;
-TapRTCResultCode code = [room updateAudioReceiverRange:range];
-```
-
-
-
-Other team members who are out of `range` will not be able to hear the player.
-
-If [3D voice](#3d-Voice) is also enabled, the distance will also affect the volume level:
-
-| Distance | Volume decay |
-| - | - |
-| `N < range/10` | 1.0 (No decay)|
-| `N >= range/10` | `range/10/N` |
-
-### Update Source Orientation
-
-After successfully entering the room, you must call this interface in Unity's Update method to update the orientation and direction of the sound source for the range audio to take effect.
-The orientation is specified by the front, right, and top coordinates of the world coordinate system, and the direction is specified by the unit vector of the front, right, and top axes of its own coordinate system.
-
-
-
-```cs
-int x = 1;
-int y = 2;
-int z = 3;
-Position position = new Position(x, y, z);
-
-float[] axisForward = new float[3] {1.0, 0.0, 0.0};
-float[] axisRight = new float[3] {0.0, 1.0, 0.0};
-float[] axisUp = new float[3] {0.0, 0.0, 1.0};
-Forward forward = new Forward(axisForward, axisRight, axisUp);
-ResultCode code = room.GetRtcRangeAudioCtrl().UpdateSelfPosition(position, forward);
-if (code == ResultCode.OK) {
- // Success
-}
-if (code == ResultCode.ERROR_NOT_RANGE_ROOM) {
- // Range audio is not enabled for this room
-}
-```
-
-```java
-import com.taptap.taprtc.TapRTCRangeAudioCtrl.Position;
-import com.taptap.taprtc.TapRTCRangeAudioCtrl.Forward;
-
-int x = 1;
-int y = 2;
-int z = 3;
-Position position = new Position(x, y, z);
-
-float[] axisForward = {1.0, 0.0, 0.0};
-float[] axisRight = {0.0, 1.0, 0.0};
-float[] axisUp = {0.0, 0.0, 1.0};
-Forward forward = new Forward(axisForward, axisRight, axisUp);
-
-ResultCode code = room.rangeAudioCtrl().updateSelfPosition(position, forward);
-if (code == ResultCode.OK) {
- // Success
-}
-if (code == ResultCode.ERROR_NOT_RANGE_ROOM) {
- // Range audio is not enabled for this room
-}
-```
-
-```objc
-TapRTCPosition position;
-position.x = 1.0;
-position.y = 2.0;
-position.z = 3.0;
-
-TapRTCAxis axisForward;
-axisForward.x = 1.0;
-axisForward.y = 0.0;
-axisForward.z = 0.0;
-TapRTCAxis axisRight;
-axisRight.x = 0.0;
-axisRight.y = 1.0;
-axisRight.z = 0.0;
-TapRTCAxis axisUp;
-axisUp.x = 0.0;
-axisUp.y = 0.0;
-axisUp.z = 1.0;
-
-TapRTCForward forward;
-forward.forward = axisForward;
-forward.rightward = axisRight;
-forward.upward = axisUp;
-TapRTCResultCode code = [room updateSelfPosition:position forward:forward];
-```
-
-
-
-The orientation has no effect on whether the voice is heard or not, so if you do not enable [3D Voice](#3d-voice), the orientation parameter can be set freely when updating the orientation of the sound source.
-However, when 3D Voice is enabled, the orientation must be set correctly to get accurate 3D sound effects.
-
-### 3D Voice
-
-Enabling 3D Voice allows you to convert voices without orientation to voices with source orientation to increase player immersion.
-This interface takes two parameters, the first specifying whether the current player can hear the 3D sound effect, and the second specifying whether the 3D voice works [within the team] (#range-audio).
-
-
-
-```cs
-bool enable3D = true;
-bool applyToTeam = true;
-ResultCode code = TapRTC.GetAudioDevice().EnableSpatializer(enable3D, applyToTeam);
-```
-
-```java
-boolean enable3D = true;
-boolean applyToTeam = true;
-boolean ok = TapRTCEngine.get().getAudioDevice().enableSpatializer(enable3D, applyToTeam);
-```
-
-```objc
-TapRTCResultCode code = [engine.audioDevice EnableSpatializer:YES applyTeam:YES];
-```
-
-
-
-
-## Error Codes
-
-Some of the operations in the above document return ResultCode, and the code examples give the error codes corresponding to some common error types.
-The complete list of error codes is shown below:
-
-
-
-```cs
-namespace TapTap.RTC
-{
- public enum ResultCode
- {
- OK = 0,
- ERROR_UNKNOWN = 1,
- ERROR_UNIMPLEMENTED = 2,
- ERROR_NOT_ON_MAIN_THREAD = 3,
- ERROR_INVAIDARGS = 4,
- ERROR_NOT_INIT = 5,
- ERROR_CONFIG_ERROR = 11,
- ERROR_NET = 21,
- ERROR_NET_TIMEOUT = 22,
- ERROR_USER_NOT_EXIST = 101,
- ERROR_ROOM_NOT_EXIST = 102,
- ERROR_DEVICE_NOT_EXIST = 103,
- ERROR_TEAM_ID_NOT_NULL = 104,
- ERROR_ALREADY_IN_ROOM = 105,
- ERROR_NO_PERMISSION = 106,
- ERROR_AUTH_FAILED = 107,
- ERROR_LIB_ERROR = 108,
- ERROR_NOT_RANGE_ROOM = 109,
- }
-}
-```
-
-```java
-public enum ResultCode {
- OK(0, "Success"),
- ERROR_UNKNOWN(1, "Unknown Error"),
- ERROR_UNIMPLEMENTED(2, "Unimplemented Functionality"),
- ERROR_NOT_ON_MAIN_THREAD(3, "Not running on the main thread"),
- ERROR_INVALID_ARGUMENT(4, "Invalid parameter"),
- ERROR_NOT_INIT(5, "Uninitialized"),
- ERROR_CONFIG_ERROR(11, "Configuration error"),
- ERROR_NET(21, "Network error"),
- ERROR_NET_TIMEOUT(22, "Network request timeout"),
- ERROR_USER_NOT_EXIST(101, "User does not exist"),
- ERROR_ROOM_NOT_EXIST(102, "Room does not exist"),
- ERROR_DEVICE_NOT_EXIST(103, "Device does not exist"),
- ERROR_TEAM_ID_NOT_NULL(104, "TeamID cannot be Zero"),
- ERROR_ALREADY_IN_ROOM(105, "It's already in the other room"),
- ERROR_NO_PERMISSION(106, "TapRTCCode_Error_NoPermission\tNo Permission"),
- ERROR_AUTH_FAILED(107, "Authorization failure"),
- ERROR_LIB_ERROR(108, "Service provider library error"),
- ERROR_NOT_RANGE_ROOM(109, "Not Support Range Room"),
-}
-```
-
-```objc
-FOUNDATION_EXPORT NSString * const TapRTCNetworkErrorDomain;
-FOUNDATION_EXPORT NSString * const TapRTCResultErrorDomain;
-
-typedef NS_ENUM(NSInteger, TapRTCResultCode) {
- TapRTCCode_OK = 0,
- TapRTCCode_Error_Unknown = 1,
- TapRTCCode_Error_Unimplemented = 2,
- TapRTCCode_Error_NotOnMainThread,
- TapRTCCode_Error_InvaidArgs,
- TapRTCCode_Error_NotInit,
-
- TapRTCCode_ConfigError_Error = 11,
- TapRTCCode_ConfigError_AppID,
- TapRTCCode_ConfigError_AppKey,
- TapRTCCode_ConfigError_ServerUrl,
- TapRTCCode_ConfigError_UserID,
- TapRTCCode_ConfigError_DeviceID,
-
- TapRTCCode_NetError_Error = 21,
- TapRTCCode_NetError_Timeout,
-
- TapRTCCode_Error_UserNotExist = 101,
- TapRTCCode_Error_RoomNotExist,
- TapRTCCode_Error_DeviceNotExist,
- TapRTCCode_Error_TeamIDNotBeZero,
- TapRTCCode_Error_AlreadyInOtherRoom,
- TapRTCCode_Error_NoPermission,
- TapRTCCode_Error_AuthFailed,
- TapRTCCode_LibError,
-};
-```
-
-
-
-## Server Side
-
-To secure the chat channel, the RTC service must be used with the game's own authentication server.
-In addition, the game's own server is used to respond to compliance callbacks and to invoke the player removal interface.
-
-### Server-Side Authentication
-
-Before a client joins a room, it must obtain a signature from your own authentication server, after which the RTC cloud verifies the signature, and only requests with valid signatures are executed, and illegal requests are blocked.
-
->Authentication Server: 1. Require signature before entering room
-Authentication Server-->>Client: 2. Generate signature to return to client
-Client->>RTC Cloud: 3. Encode the signature in the request and send it to the real-time voice server
-RTC Cloud-->>Client: 4. Verify the content of the request and the signature and perform the subsequent operations
-`} />
-
-1. The client requests a signature from the game's authentication server before entering the room;
-2. The authentication server generates a signature to return to the client based on the [authentication key algorithm](#authentication-key-algorithm) described below;
-3. The client receives the signature, encrypts it in the request, and sends it to the RTC server;
-4. The RTC server performs a verification of the request content and signature, and performs the subsequent actual operation after passing the verification.
-
-Signatures use a combination of the **HMAC-SHA1** algorithm and Base64.
-For different requests, your app generates different signatures (see format descriptions below). Overall, signing is the process of signing player and room information using a specific key (in this case, we use the application's Master Key).
-
-#### Authentication Key Algorithm
-
-The signature generation process used for authentication involves **plaintext**, **key**, and **encryption algorithm**.
-
-##### Plaintext
-
-The plaintext is a json string consisting of the following fields (in any order)
-
-
-
-| Field | Type/Length | Description |
-| :--------- | :------------- | :----------------------------------------------------------- |
-| `userId` | `string` | An identifier of the user entering the room |
-| `appId` | `string` | The game's Client ID |
-| `expireAt` | `unsigned int/4` | Expiration time (current time + expiration date (in seconds, recommended value: 300s)) |
-| `roomId` | `string` | Room ID |
-
-##### Key
-
-The `Master Key` (i.e. `Server Secret`) of the game.
-Both the `Client ID` and `Server Secret` can be viewed in **Developer Center > Your game > Game Services > Configuration**.
-
-##### Encryption Algorithm
-
-The encryption algorithm uses a combination of **HMAC-SHA1** algorithm and Base64, similar to the JWT format.
-The generated result contains two parts: payload (plaintext) and sign (encrypted string).
-
-1. Construct the JSON string according to the fields in the table above.
-
-2. Base64-encode the JSON string from the previous step to obtain the payload.
-
-3. Generate the sign using **HMAC-SHA1** on the payload with **key**.
-
-4. Use `.` to join the payload and the token.
-
-Note: The JSON string itself is field-order independent, but the **spliced payload and the payload used to generate the sign must be in the same field order** or they will not pass the checksum in the RTC cloud.
-
-The following sample code in Java and Go is provided for reference:
-
-
-Java example
-
-```java
-import com.google.gson.Gson;
-import org.junit.Test;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import java.nio.charset.StandardCharsets;
-import static org.junit.Assert.*;
-
-import java.time.Instant;
-import java.util.Base64;
-
-public class JUnitTestSuite {
-
- private static final String MAC_NAME = "HmacSHA1";
-
- @Test
- public void testToken() throws Exception {
- String masterKey = "masterKey";
- Token t = new Token();
- t.appId = "appId";
- t.userId ="user_test";
- t.roomId ="room_test";;
-
- int expTime = (int) Instant.now().getEpochSecond() + 5 * 60;
- t.expireAt = expTime;
-
- // server authBuff to your SDK Client
- String authBuff = genToken(t, masterKey);
- assertNotNull(authBuff);
- }
-
- private String genToken(Token token, String key) throws Exception {
- Gson gson = new Gson();
- String t = gson.toJson(token);
- String payload = Base64.getEncoder().encodeToString(t.getBytes(StandardCharsets.UTF_8));
- byte[] pEncryptOutBuf = hmacSHA1Encrypt(payload.getBytes(StandardCharsets.UTF_8), key);
- String sign = Base64.getEncoder().encodeToString(pEncryptOutBuf);
- return payload + "." + sign;
- }
-
-
- byte[] hmacSHA1Encrypt(byte[] text, String key) throws Exception {
- byte[] data = key.getBytes(StandardCharsets.UTF_8);
- SecretKey secretKey = new SecretKeySpec(data, MAC_NAME);
- Mac mac = Mac.getInstance(MAC_NAME);
- mac.init(secretKey);
- return mac.doFinal(text);
- }
-
- class Token {
- String userId;
- String appId;
- String roomId;
- long expireAt;
- }
-}
-```
-
-
-
-
-Go example
-
-```go
-package configs
-
-import (
- "crypto/hmac"
- "crypto/sha1"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-)
-
-
-func TestToken(t *testing.T) {
- assert := assert.New(t)
- t1 := &Token{
- UserId: "appId",
- AppId: "user_test",
- RoomId: "roomId_test",
- ExpireAt: time.Now().Unix() + 5*60,
- }
- authBuff := GenToken(t1, "masterKey")
- assert.NotEmpty(authBuff)
- fmt.Println(authBuff)
-}
-
-
-const (
- sep = "."
-)
-
-func GenToken(t *Token, masterKey string) string {
- b, err := json.Marshal(t)
- if err != nil {
- return ""
- }
- payload := base64.StdEncoding.EncodeToString(b)
- sign := base64.StdEncoding.EncodeToString(HmacSHA1(masterKey, payload))
- return payload + sep + sign
-
-}
-
-
-func HmacSHA1(key string, data string) []byte {
- mac := hmac.New(sha1.New, []byte(key))
- mac.Write([]byte(data))
- return mac.Sum(nil)
-}
-
-type Token struct {
- UserId string `json:"userId,omitempty"`
- AppId string `json:"appId,omitempty"`
- RoomId string `json:"roomId,omitempty"`
- ExpireAt int64 `json:"expireAt,omitempty"`
-}
-```
-
-
-
-#### Deployment Method
-
-Since the encryption key uses `Server Secret`, the logic of the encryption algorithm must be implemented on the server side. **Do not implement the encryption logic on the client side**.
-
-#### Usage
-
-The game's own authentication server generates the encryption string and sends it to the client. The client then passes the appropriate authentication information when calling the [join room interface](#join-a-room).
-
-The C# SDK also provides a `GenToken` method for testing when integrating the SDK on the client side.
-For example, client-side developers can test the functionality of adding rooms on the client side before waiting for server-side developers to implement and deploy the appropriate interfaces.
-Another example is that the client developer can compare the encrypted string generated by the SDK's own `GenToken` with the encrypted string generated by the server to verify that the server has implemented the encryption algorithm correctly.
-
-```cs
-var authBuffer = AuthBufferHelper.GenToken(appId, roomId, userId, masterKey);
-```
-
-Note that since this method requires `Server Secret` to be passed as a parameter, **it is intended for internal testing and development only, and should not be used in published code or installation packages**.
-If you are concerned about `Server Secret` being leaked into external code or installation packages due to human error, or if you want to minimize the number of internal developers having access to `Server Secret` for security reasons, it is recommended that you do not use the `GenToken` method provided by the SDK, and that you generate the encrypted string using the game's own authentication server for internal testing as well.
-
-### Compliance Callback
-
-
-After you set the callback address in the RTC dashboard (**Developer Center > Your game > Game Services > RTC > Settings**), the set callback address will be called if the voice content is illegal.
-
-The callback address should be a URL of the HTTP(S) protocol interface, support the POST method, and use UTF-8 for data encoding.
-
-Example POST body for callback:
-
-```json
-{
- "HitFlag":true,
- "Msg":"Illegal message",
- "ScanFinishTime":1634893736,
- "ScanStartTime":1634893734,
- "Scenes":[
- "default"
- ],
- "VoiceFilterPiece":[
- {
- "Duration":14000,
- "HitFlag":true,
- "Info":"Illegal message",
- "MainType":"abuse",
- "Offset":0,
- "PieceStartTime":1634893734,
- "RoomId":"1234",
- "UserId":"123456",
- "VoiceFilterDetail":[
- {
- "EndTime":0,
- "KeyWord":"Illegal keyword",
- "Label":"abuse",
- "Rate":"0.00",
- "StartTime":0
- }
- ]
- }
- ]
-}
-```
-
-You can get the `sign` field in the callback header to verify that the request is coming from the RTC cloud.
-
-#### Compliance Callback Verification Algorithm
-
-1. Append the `POST` prefix to the POST body of the callback to get the payload:
-
- ```
- POST{"HitFlag":true,"Msg":"Illegal message",/* Other logic */}
- ```
-
- Note:
-
- - Please read the JSON content directly from the HTTP request body**. Deserializing to the programming language data structure may change the order of the fields, resulting in a validation failure.
- - POST and body are directly connected without spaces in between.
-
-2. Perform HMAC-SHA1 encryption on the payload. The key is the game's `Server Secret`.
-
-3. BASE64-encode the result of the previous step to get the sign.
-
-4. Compare it with the value of the sign field in the HTTP header of the callback. If it is the same, then the request is coming from the RTC cloud.
-
-Here is a sample Go code for reference:
-
-
-Go example
-
-```go
-package main
-
-import (
- "crypto/hmac"
- "crypto/sha1"
- "encoding/base64"
- "github.com/labstack/echo/v4"
- "io/ioutil"
- "net/http"
-)
-
-
-func testCallback(c echo.Context) error {
- sign := c.Request().Header.Get("sign")
- body, _ := ioutil.ReadAll(c.Request().Body)
- checkGMESign(sign, "yourMasterKey", string(body))
- return c.NoContent(http.StatusOK)
-}
-
-
-func checkGMESign(signature, secretKey, body string) bool {
- sign := genSign(secretKey, body)
- return sign == signature
-}
-
-func genSign(secretKey, body string) string {
- content := "POST" + body
- a := hmacSHA1(secretKey, content)
- return base64.StdEncoding.EncodeToString(a)
-}
-
-func hmacSHA1(key string, data string) []byte {
- mac := hmac.New(sha1.New, []byte(key))
- mac.Write([]byte(data))
- return mac.Sum(nil)
-}
-```
-
-
-
-### Remove Players
-
-In some scenarios, the game may need to kick players out of a room, such as when illegal content is involved.
-You can call the RTC service's REST API from your own server to fulfill this need.
-
-#### Request Format
-
-For POST and PUT requests, the request body must be in JSON format and the Content-Type of the HTTP header must be set to `application/json`.
-
-The request is authenticated by the key/value pairs contained in the HTTP header, with the following parameters:
-
-Key|Value|Meaning|Source
----|----|---|---
-`X-LC-Id`|`{{appid}}`|The `App Id` (`Client Id`) of the current application|Can be viewed in the console
-`X-LC-Key`|`{{masterkey}},master`|The `Master Key` (`Server Secret`) of the current application|Can be viewed in the console
-
-#### Base URL
-
-The Base URL for REST API requests (the `{{host}}` in the curl examples) is the custom API domain of your app. You can update or find it on the Developer Center. See [Domain](/sdk/storage/guide/setup-dotnet#domain) for more details.
-
-#### REST API
-
-```sh
-curl -X DELETE \
--H "Content-Type: application/json" \
--H "X-LC-Id: {{appId}}" \
--H "X-LC-Key: {{masterKey}},master" \
--d '{"roomId":"YOUR-ROOM-ID", "userId":"YOUR-USER-ID"}' \
-https://{{host}}/rtc/v1/room/member
-```
-
-The HTTP status code in response to a successful removal is `200`, and the HTTP status code in response to an error is the appropriate error code, e.g. 401 if you do not have permission.
-
-Note that **after a player has been removed, the player can rejoin the room and speak again after joining the room. **
-**The game must also implement the appropriate blocking logic on its own authentication server** so that no signature is issued when the player rejoins the room, preventing players from circumventing the restriction by rejoining the room.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/tap-download.mdx b/i18n/en/docusaurus-plugin-content-docs/current/tap-download.mdx
deleted file mode 100644
index be3d42b21..000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/tap-download.mdx
+++ /dev/null
@@ -1,20 +0,0 @@
----
-title: Downloads
----
-
-## SDK
-
-- [TapSDK Unity](https://github.com/taptap/TapSDK-Unity/releases)
-- [TapSDK Android](https://github.com/taptap/TapSDK-Android/releases)
-- [TapSDK iOS](https://github.com/taptap/TapSDK-iOS/releases)
-- [LeanCloud C# SDK](https://github.com/leancloud/csharp-sdk/releases)
-
-## Demos
-
-- [Unity](https://github.com/taptap/TapSDK-Unity-Demo)
-- [Android](https://github.com/taptap/TapSDK-Android)
-- [iOS](https://github.com/taptap/TapSDK-iOS)
-
-## Login Button
-
-Click [icon.zip](https://capacity-files.lcfile.com/xLuT4Mi3gqy5K518LbRxsBo2WiwR0CHx/taptap-login-button.zip) to download the TapTap Login Button.
diff --git a/leancloud/conf/docusaurus.config.js b/leancloud/conf/docusaurus.config.js
deleted file mode 100644
index 85d95cfef..000000000
--- a/leancloud/conf/docusaurus.config.js
+++ /dev/null
@@ -1,173 +0,0 @@
-// @ts-check
-
-const PREVIEW = process.env.PREVIEW ?? "false";
-
-/** @type {import('@docusaurus/types').Config} */
-const config = {
- title: "LeanCloud 开发者文档",
- url: "https://docs.leancloud.cn",
- baseUrl: "/",
- onBrokenLinks: "throw",
- onBrokenMarkdownLinks: "warn",
- favicon: "img/lc-favicon.ico",
- trailingSlash: true,
- customFields: {
- searchUrl: "https://lc-doc-search-api.leanapp.cn/search",
- upItemListIndexUrl: "https://lc-doc-search-check-log.leanapp.cn/api/check-log-up",
- aiSearchUrl :"https://tds-doc-search-ai-api.ap-sg.tdsapps.com/api/ai-search?type=LC",
- aiSearchEnUrl :"https://tds-doc-search-ai-api.ap-sg.tdsapps.com/api/ai-search?type=LCen",
- searchProviderName: "LeanDB Elasticsearch",
- searchProviderWebsite: "https://docs.leancloud.cn/sdk/engine/database/es/",
- mainDomainHost: "https://www.leancloud.cn",
- dcDomainHost: "https://www.leancloud.cn",
- },
-
- i18n: {
- localeConfigs: {
- en: {
- label: "English",
- },
- "zh-Hans": {
- label: "简体中文",
- },
- },
- defaultLocale: "zh-Hans",
- locales: ["zh-Hans", "en"],
- },
-
- presets: [
- [
- "classic",
- /** @type {import('@docusaurus/preset-classic').Options} */
- ({
- docs: {
- sidebarPath: require.resolve("./sidebars.js"),
- routeBasePath: "/",
- lastVersion: "current",
- versions: {
- current: {
- label: "v3",
- },
- },
- },
- theme: {
- customCss: require.resolve("./src/styles/index.scss"),
- },
- googleAnalytics: {
- trackingID: "UA-73963350-1",
- },
- }),
- ],
- ],
-
- themeConfig:
- /** @type {import('@docusaurus/preset-classic').ThemeConfig} */
- ({
- navbar: {
- items: [
- {
- label: "文档首页",
- to: "/",
- position: "right",
- activeBaseRegex: "^/(?!.+)",
- },
- {
- label: 'API 文档',
- position: 'right',
- items: [
- {
- label: 'Android/Java SDK API',
- href: 'https://leancloud.github.io/java-unified-sdk/',
- },
- {
- label: 'Objective-C SDK API',
- href: 'https://leancloud.github.io/objc-sdk/',
- },
- {
- label: 'Swfit SDK API',
- href: 'https://leancloud.github.io/swift-sdk/',
- },
- {
- label: 'Flutter 数据存储 SDK API',
- href: 'https://pub.dev/documentation/leancloud_storage/latest/leancloud_storage/leancloud_storage-library.html',
- },
- {
- label: 'Flutter 即时通讯 SDK API',
- href: 'https://pub.dev/documentation/leancloud_official_plugin/latest/leancloud_plugin/leancloud_plugin-library.html',
- },
- {
- label: 'JavaScript 数据存储 SDK API',
- href: 'https://leancloud.github.io/javascript-sdk/docs/',
- },
- {
- label: 'JavaScript 即时通讯 SDK API',
- href: 'https://leancloud.github.io/js-realtime-sdk/docs/',
- },
- {
- label: 'JavaScript 多人在线对战 SDK API',
- href: 'https://leancloud.github.io/Play-SDK-JS/doc/global.html',
- },
- {
- label: 'Python SDK API',
- href: 'https://leancloud.github.io/python-sdk/',
- },
- {
- label: 'PHP SDK API',
- href: 'https://leancloud.github.io/php-sdk/',
- },
- {
- label: 'Go SDK API',
- href: 'https://pkg.go.dev/github.com/leancloud/go-sdk/leancloud',
- },
- {
- label: '.NET SDK API',
- href: 'https://leancloud.github.io/csharp-sdk/html/',
- }
- ],
- },
- {
- label: '资源',
- position: 'right',
- items: [
- {
- label: 'SDK',
- href: '/sdk/sdk-page/',
- },
- {
- label: 'Demo',
- to: '/demo',
- }
- ],
- },
- {
- label: "云课堂",
- to: "/classroom",
- position: "right",
- },
- {
- type: "localeDropdown",
- position: "right",
- }
- ],
- },
- prism: {
- theme: require("./src/theme/prism-taptap"),
- additionalLanguages: ["csharp", "java", "php", "groovy", "swift", "dart"],
- },
- image: "/img/logo.svg",
- metadata: [
- {
- name: "keywords",
- content: "leancloud 开发者 文档",
- },
- ],
- colorMode: {
- defaultMode: "light",
- disableSwitch: true,
- },
- }),
-
- plugins: ["docusaurus-plugin-sass"],
-};
-
-module.exports = config;
diff --git a/leancloud/conf/env.ts b/leancloud/conf/env.ts
deleted file mode 100644
index ef24d4d96..000000000
--- a/leancloud/conf/env.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export const BRAND: string = "leancloud";
-export const REGION: string = "cn";
-
-// Cloud Engine
-export const CLI_BINARY: string = BRAND === "tds" ? "tds" : "lean";
-export const HAS_SUB_DOMAIN: boolean = REGION === "global";
-export const HAS_ENGINE_CDN_DOMAIN: boolean = REGION === 'cn'
diff --git a/leancloud/conf/override.scss b/leancloud/conf/override.scss
deleted file mode 100644
index a682fb1f3..000000000
--- a/leancloud/conf/override.scss
+++ /dev/null
@@ -1,72 +0,0 @@
-/* source: ~infima/dist/css/default/default.css */
-@import "font";
-
-:root {
- --ifm-code-font-size: 95%;
- --ifm-color-emphasis-300: var(--tap-grey2);
- --ifm-color-primary: #2c97e8;
- --ifm-color-primary-dark: #188ae0;
- --ifm-color-primary-darker: #1782d4;
- --ifm-color-primary-darkest: #136bae;
- --ifm-color-primary-light: #45a3eb;
- --ifm-color-primary-lighter: #51a9ec;
- --ifm-color-primary-lightest: #77bcf0;
- --ifm-dropdown-hover-background-color: var(--tap-grey0);
- --ifm-dropdown-link-color: var(--tap-grey6);
- --ifm-global-shadow-md: var(--tap-box-shadow-3);
- --ifm-hr-border-color: var(--tap-grey2);
- --ifm-leading: 1em;
- --ifm-menu-color-background-active: #eef6fc;
- --ifm-menu-color-background-hover: #fafafa;
- --ifm-menu-link-padding-horizontal: 19px;
- --ifm-menu-link-padding-vertical: 7px;
- --ifm-menu-link-sublist-icon: url('data:image/svg+xml;utf8,');
- --ifm-navbar-height: 56px;
- --ifm-navbar-link-color: var(--tap-grey6);
- --ifm-navbar-padding-horizontal: 24px;
- --ifm-scrollbar-size: 6px;
- --ifm-scrollbar-thumb-background-color: #e4e7e8;
- --ifm-scrollbar-thumb-hover-background-color: #e4e7e8;
- --ifm-scrollbar-track-background-color: transparent;
- --ifm-table-stripe-background: var(--ifm-table-background);
- --ifm-toc-border-color: var(--tap-grey2);
- --docusaurus-highlighted-code-line-bg: #303641;
-}
-
-.table-of-contents {
- @include tap-font-14;
- position: relative;
-
- ul {
- @include tap-font-12;
- }
-
- strong {
- @include tap-font-regular;
- }
-
- &__link--active {
- &:before {
- content: "";
- position: absolute;
- left: -1px;
- margin-top: 0.25em;
- width: 2px;
- height: 1em;
- background-color: var(--ifm-color-primary);
- }
- }
-}
-
-.pagination-nav {
- padding-top: 2.4rem;
-
- &__sublabel {
- color: var(--tap-grey5);
- }
-
- &__label {
- margin-top: 4px;
- color: var(--tap-grey6);
- }
-}
diff --git a/leancloud/conf/sidebars.js b/leancloud/conf/sidebars.js
deleted file mode 100644
index 9c9daf379..000000000
--- a/leancloud/conf/sidebars.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// @ts-check
-
-/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
-const sidebars = {
- sdk: [
- {
- type: "autogenerated",
- dirName: "sdk",
- },
- ],
-};
-
-module.exports = sidebars;
diff --git a/leancloud/docs/sdk/_partials/android-package-visibility.mdx b/leancloud/docs/sdk/_partials/android-package-visibility.mdx
deleted file mode 100644
index dcfd0fe87..000000000
--- a/leancloud/docs/sdk/_partials/android-package-visibility.mdx
+++ /dev/null
@@ -1,39 +0,0 @@
-Android 11(API level 30)之后加强了隐私保护策略,引入了大量变更和限制,其中一个重要变更——[软件包可见性](https://developer.android.com/about/versions/11/privacy/package-visibility),将会导致第三方应用无法拉起 TapTap 客户端,从而影响 TapTap 相关功能的正常使用,包括但不限于更新唤起 TapTap、购买验证等功能。
-
-如果没有完成适配,Android 版本为 11 及更高版本的客户端打开游戏会提示「本游戏需要最新版 TapTap 服务支持」,无法正常进入游戏。异常呈现如下图所示:
-
-![图片描述](/img/android-package-visibility-android11.png)
-
-对此提供如下两种适配方案:
-
-**方案一:**
-
-编译时将 `targetSdkVersion` 改为 29(目前设置成 >= 30 会触发该问题)。
-
-**方案二:**
-
-1. 将 gradle build tools 改为 4.1.0+:
-
- ```java
- classpath 'com.android.tools.build:gradle:4.1.0'
- ```
-
-2. 在 AndroidManifest.xml 里添加 `` 标签中的内容:
-
- ```xml
-
-
-
-
-
-
-
-
-
-
-
-
- ```
diff --git a/leancloud/docs/sdk/_partials/languages.mdx b/leancloud/docs/sdk/_partials/languages.mdx
deleted file mode 100644
index 9433b235d..000000000
--- a/leancloud/docs/sdk/_partials/languages.mdx
+++ /dev/null
@@ -1,133 +0,0 @@
-import MultiLang from "/src/docComponents/MultiLang";
-
-
-
-<>
-
-```cs
-TapCommon.SetLanguage(TapLanguage.AUTO);
-```
-
-支持如下语言:
-
-```cs
-namespace TapTap.Common
-{
- public enum TapLanguage
- {
- AUTO = 0, // 自动
- ZH_HANS = 1, // 简体中文
- EN = 2, // 英文
- ZH_HANT = 3, // 繁体中文
- JA = 4, // 日文
- KO = 5, // 韩文
- TH = 6, // 泰文
- ID = 7, // 印尼语
- DE = 8, // 德语
- ES = 9, // 西班牙语
- FR = 10, // 法语
- PT = 11, // 葡萄牙语
- RU = 12, // 俄语
- TR = 13, // 土耳其语
- VI = 14, // 越南语
- }
-}
-```
-
->
-
-<>
-
-```java
-TapLanguage lang = 0;
-TapBootstrap.setPreferredLanguage(TapLanguage.AUTO);
-```
-
-支持如下语言:
-
-```
-AUTO 跟随系统
-ZH_HANS 简体中文
-EN 英文
-ZH_HANT 繁体中文
-JA 日文
-KO 韩文
-TH 泰文
-ID 印尼语
-DE 德语
-ES 西班牙语
-FR 法语
-PT 葡萄牙语
-RU 俄语
-TR 土耳其语
-VI 越南语
-```
-
->
-
-<>
-
-```objc
-[TapBootstrap setPreferredLanguage:TapLanguageType_Auto];
-```
-
-支持如下语言:
-
-```objc
-typedef NS_ENUM (NSInteger, TapLanguageType) {
- TapLanguageType_Auto = 0, // 自动
- TapLanguageType_zh_Hans, // 简体中文
- TapLanguageType_en, // 英文
- TapLanguageType_zh_Hant, // 繁体中文
- TapLanguageType_ja, // 日文
- TapLanguageType_ko, // 韩文
- TapLanguageType_th, // 泰文
- TapLanguageType_id, // 印度尼西亚语
- TapLanguageType_de, // 德语
- TapLanguageType_es, // 西班牙语
- TapLanguageType_fr, // 法语
- TapLanguageType_pt, // 葡萄牙语
- TapLanguageType_ru, // 俄语
- TapLanguageType_tr, // 土耳其语
- TapLanguageType_vi, // 越南语
-};
-```
-
->
-
-<>
-
-```cpp
-TapUECommon::SetLanguage(ELanguageType::AUTO);
-```
-
-支持如下语言:
-
-```cpp
-UENUM(BlueprintType)
-enum class ELanguageType : uint8
-{
- AUTO = 0, // 自动
- ZH, // 简体中文
- EN, // 英文,海外默认语言
- ZHTW, // 繁体中文
- JA, // 日语
- KO, // 韩语
- TH, // 泰文
- ID, // 印尼文
- DE, // 德语
- ES, // 西班牙语
- FR, // 法语
- PT, // 葡萄牙语
- RU, // 俄语
- TR, // 土耳其语
- VI, // 越南语
-};
-```
-
->
-
-
-
-「自动」会尝试根据系统语言设置语言,如果系统语言不在上述支持的语言之中,那么会根据 [SDK 初始化时配置的区域](/sdk/start/quickstart/#初始化)设置语言。
-区域为中国大陆时会设置为简体中文,否则会设置为英文。
diff --git a/leancloud/docs/sdk/_partials/setup-domain.mdx b/leancloud/docs/sdk/_partials/setup-domain.mdx
deleted file mode 100644
index a09a82153..000000000
--- a/leancloud/docs/sdk/_partials/setup-domain.mdx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { Conditional } from "/src/docComponents/conditional";
-
-
-
-
-
-使用 TDS 提供的云服务,初始化客户端 SDK 需要在 `server_url` 处填入 API 域名,可前往 **开发者中心 > 你的游戏 > 游戏服务 > 应用配置 > 域名配置** 获取 TDS 提供的 **共享域名**。
-
-
-
-
-
-使用 TDS 提供的云服务需要绑定 **API 自定义域名**,以便和其他厂商的应用隔离入口,避免其他应用受到 DDoS 攻击时相互牵连。
-
-初始化客户端 SDK 时 `server_url` 处填入的就是 API 域名。
-
-### 绑定 API 域名
-
-绑定 API 域名的前提是,你拥有一个**已经完成备案的域名**。
-
-
-点击查看 API 域名绑定步骤
-
-假设你的域名为 `example.com`,API 域名绑定步骤和状态如下:
-
-![domain guide](https://capacity-files.lcfile.com/RonCpipde80meo5BL8fxrjHTee39Wit6/domain-guide.png)
-
-- 进入 **开发者中心 > 你的游戏 > 游戏服务 > 应用配置 > 域名配置 > API** ,点击 **「绑定新域名」** 按钮。API 域名不支持直接绑定裸域名,需要在主域名的前面添加自定义名称,也就是绑定一个子域名,比如这里你可以绑定 `api.example.com`。
-- 控制台显示 **「正在检查备案信息」**,请等待一会儿。
-- 如果域名没有完成备案,将会显示 **「绑定失败」**。
-- 域名备案检查通过,域名下方显示 **「请配置 DNS」**。
-- 此时需要到你的域名服务商控制台,进入域名解析设置页面,添加一条记录,记录类型为 A(A 记录可以将域名指向一个 IP 地址),请将前面填到开发者后台的自定义域名和 **「推荐 DNS 配置」** 下方 A 记录值复制到对应位置。
-- DNS 解析记录和证书申请(如果选择了自动管理 SSL 证书)都需要一定时间,请耐心等待。记录生效后,控制台便会显示 **「已绑定」**。
-
-
-
-绑定成功后,初始化 SDK 时请传入绑定的自定义域名 `https://api.example.com`。这里用的是示例,请换成你自己绑定的 API 域名,注意不要遗漏前面的 `https://`。
-
-配置域名需要一定的时间,TDS 为开发者提供了 **共享域名** 在游戏测试时使用,但共享域名没有可用性保证,容易受到 DDoS 攻击影响。游戏上线前,一定要确认使用的 API 访问域名是开发者自己绑定的域名,请勿将共享域名用于生产环境。
-
-### 绑定文件域名
-
-如果使用了数据存储中的文件服务,需要绑定**文件访问域名**。
-
-可前往 **开发者中心 > 你的游戏 > 游戏服务 > 应用配置 > 域名配置 > 文件** 绑定文件域名,步骤和 API 自定义域名基本相同。但有两点不一样:
-
-1. API 域名解析使用 A 记录,文件域名解析使用 CNAME 记录。文件域名同样不支持绑定裸域名,需要绑定子域名。例如你的主域名是 `example.com`,可以绑定文件域名为 `files.example.com`。
-2. 绑定成功后,还需在 **开发与构建 > 数据存储 > 文件 > 设置 > 文件访问地址** 点击「修改」按钮进行切换。
-
-:::info
-
-每个子域名只能绑定到一个游戏,且 API 域名和文件域名不可使用同样的子域名。如果你已经在 TDS 控制台绑定了某个子域名,重复绑定时控制台会显示「该域名已经被其他应用所绑定」,此时可以更换同一主域名下不同的子域名,来完成后续绑定步骤。
-
-:::
-
-
-
-
-
-
-
-请参考 [域名绑定指南](/sdk/domain/guide/)。
-
-
diff --git a/leancloud/docs/sdk/_partials/tap-login-profile.mdx b/leancloud/docs/sdk/_partials/tap-login-profile.mdx
deleted file mode 100644
index d1a31d605..000000000
--- a/leancloud/docs/sdk/_partials/tap-login-profile.mdx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Conditional } from "/src/docComponents/conditional";
-
-这里获取的 `TapTapAccount` 根据游戏申请的[授权范围](/sdk/taptap-login/guide/start/#不同的授权范围)有所差异。
-
-其中可能会包含如下信息:
-
-
-
-| 参数 | 说明 |
-| --------- | ---------------------------------------------------------------------------------------------------------------------------- |
-| `accessToken` | 用户访问凭证 |
-| `openid` | 通过用户信息和游戏信息生成的**用户唯一标识**,每个玩家在每个游戏中的 `openid` 都是不一样的 |
-| `unionid` | 通过用户信息加上厂商信息生成的用户唯一标识,一个玩家在同一个厂商的所有游戏中 `unionid` 都是一样的,不同厂商下 `unionid` 不同 |
-| `name` | 玩家在 TapTap 平台的昵称 |
-| `avatar` | 玩家在 TapTap 平台的头像 url
-| `email` | 用户在 TapTap 平台注册使用的邮箱 |
-
-
-
-
-
-| 参数 | 说明 |
-| --------- | ---------------------------------------------------------------------------------------------------------------------------- |
-| `accessToken` | 用户访问凭证 |
-| `openid` | 通过用户信息和游戏信息生成的**用户唯一标识**,每个玩家在每个游戏中的 `openid` 都是不一样的 |
-| `unionid` | 通过用户信息加上厂商信息生成的用户唯一标识,一个玩家在同一个厂商的所有游戏中 `unionid` 都是一样的,不同厂商下 `unionid` 不同 |
-| `name` | 玩家在 TapTap 平台的昵称 |
-| `avatar` | 玩家在 TapTap 平台的头像 url
-| `email` | 用户在 TapTap 平台注册使用的邮箱 |
- |
-
-
-
-`openid` 和 `unionid` 使用[标准的 Base64](https://datatracker.ietf.org/doc/html/rfc4648#section-4)(带 Padding)编码,包含的字符有 `A-Za-z0-9+/=`。`openid` 和 `unionid` 长度最大值为 50 个字符。
-
-:::info
-由于 `unionid` 与游戏所属厂商有强关联性,因此 `unionid` 适用于如下场景:厂商使用测试服进行付费删档等测试,正式服需要对于之前参与测试的老玩家进行返利等操作。因为同一个玩家在同一个厂商下的所有游戏中的 `unionid` 不变。
-一个游戏在厂商转移后同一个用户的 `unionid` 会发生改变,如果游戏使用了 `unionid`,TDS 技术支持会在转移前通过工单和游戏开发者确认相关数据的处理方案,保证迁移前后用户数据不错乱。
-:::
diff --git a/leancloud/docs/sdk/_partials/unity-sdk-installation.mdx b/leancloud/docs/sdk/_partials/unity-sdk-installation.mdx
deleted file mode 100644
index f1dcda6fb..000000000
--- a/leancloud/docs/sdk/_partials/unity-sdk-installation.mdx
+++ /dev/null
@@ -1,79 +0,0 @@
-import CodeBlock from "@theme/CodeBlock";
-import sdkVersions from "/src/docComponents/sdkVersions";
-
-SDK 可以**通过 Unity Package Manager 导入或手动导入**,二者任选其一。请根据项目需要选择。
-
-#### 方法一:使用 Unity Package Manager
-从 3.29.1 版本开始, SDK 修改 JSON 解析库为 `Newtonsoft-json`,如果当前工程已接入该依赖库,则不需额外处理,否则需在 `Packages/manifest.json` 添加如下依赖:
-
-```
-"com.unity.nuget.newtonsoft-json":"3.2.1"
-```
-
-##### NPMJS 安装
-
-从 3.25.0 版本开始,TapSDK 支持了 NPMJS 安装,优势是只需要配置版本号,并且支持嵌套依赖。
-
-在项目的 `Packages/manifest.json` 文件中添加以下依赖:
-
-
- {`"dependencies":{
- ${props.npmDeps.map(dep => `"${dep}":"${sdkVersions.taptap.unity}",`).join('\n ')}
- }`}
-
-
-但需要注意的是,要在 `Packages/manifest.json` 中 `dependencies` 同级下声明 `scopedRegistries`:
-
-
-{props.NoNeedLC ?
- `"scopedRegistries": [
- {
- "name": "NPMJS",
- "url": "https://registry.npmjs.org/",
- "scopes": ["com.tapsdk", "com.taptap"]
- }
- ]
- ` :
- `"scopedRegistries": [
- {
- "name": "NPMJS",
- "url": "https://registry.npmjs.org/",
- "scopes": ["com.tapsdk", "com.taptap", "com.leancloud"]
- }
- ]
- `
-}
-
-
-##### GitHub 安装
-
-在项目的 `Packages/manifest.json` 文件中添加以下依赖:
-
-
- {`"dependencies":{
- ${props.githubDeps.map(dep => `"${dep.package}":"${dep.url}",`).join('\n ')}
- }`}
-
-
-在 Unity 顶部菜单中选择 **Window > Package Manager** 可查看已经安装在项目中的包。
-
-#### 方法二:手动导入
-
-1. 在 [下载页](/tap-download) 找到 **TapSDK Unity** 下载地址,下载 `TapSDK-UnityPackage.zip` 。
-
-2. 在 Unity 项目中依次转到 **Assets > Import Packages > Custom Packages**,从解压后的 `TapSDK-UnityPackage.zip` 中,选择希望在游戏中使用的 TapSDK 包导入,其中:
-
-