Metadata-Version: 2.4
Name: communityone
Version: 1.3.0
Summary: Official Python SDK for CommunityOne API
Home-page: https://github.com/CommunityOne-io/communityone-sdk
Author: CommunityOne
Author-email: support@communityone.io
License: MIT
Project-URL: Homepage, https://communityone.io
Project-URL: Documentation, https://api.communityone.io/v1/documentation
Project-URL: Bug Tracker, https://github.com/CommunityOne-io/communityone-sdk/issues
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.25.0
Requires-Dist: aiohttp>=3.8.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license
Dynamic: license-file
Dynamic: project-url
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# CommunityOne SDK

[![PyPI version](https://img.shields.io/pypi/v/communityone.svg)](https://pypi.org/project/communityone/)
[![Python versions](https://img.shields.io/pypi/pyversions/communityone.svg)](https://pypi.org/project/communityone/)

Official Python SDK for interacting with the [CommunityOne](https://communityone.io) API. This SDK provides both synchronous and asynchronous methods to interact with CommunityOne's API endpoints.

## About CommunityOne

[CommunityOne](https://communityone.io) is a platform that helps Discord communities grow and engage their members through quests, rewards, and gamification.

## Installation

You can install the package using pip:

```bash
pip install communityone
```

## Quick Start

```python
from communityone import CommunityOneSDK

# Initialize the SDK with your server ID and API key
sdk = CommunityOneSDK(server_id=YOUR_SERVER_ID, api_key="YOUR_API_KEY")

# Get all custom quests
custom_quests = sdk.get_custom_quests()

# Get player information
player_info = sdk.get_player_info(discord_user_id="DISCORD_USER_ID")

# Complete a custom quest
result = sdk.complete_custom_quest(custom_quest_id="CUSTOM_QUEST_ID", discord_user_id="DISCORD_USER_ID")

# Get completed members for a quest
completed_members = sdk.get_completed_members(custom_quest_id="CUSTOM_QUEST_ID")

# Get global leaderboard
leaderboard = sdk.get_global_leaderboard()

# Check whether members were flagged as suspicious accounts
suspicious = sdk.check_suspicious_accounts(user_ids=["DISCORD_USER_ID"])

# Get activity insights for members
insights = sdk.get_member_insights(user_ids=["DISCORD_USER_ID"])

# Get the outcomes recorded for an invite code
outcomes = sdk.get_invite_outcomes(invite_code="INVITE_CODE")
```

## Async Support

The SDK also provides async methods for all operations:

```python
import asyncio
from communityone import CommunityOneSDK

async def main():
    sdk = CommunityOneSDK(server_id=YOUR_SERVER_ID, api_key="YOUR_API_KEY")
    
    # Get custom quests asynchronously
    custom_quests = await sdk.get_custom_quests_async()
    
    # Get player information asynchronously
    player_info = await sdk.get_player_info_async("DISCORD_USER_ID")
    
    # Complete a custom quest asynchronously
    result = await sdk.complete_custom_quest_async(custom_quest_id="CUSTOM_QUEST_ID", discord_user_id="DISCORD_USER_ID")
    
    # Get completed members asynchronously
    completed_members = await sdk.get_completed_members_async(custom_quest_id="CUSTOM_QUEST_ID")
    
    # Get global leaderboard asynchronously
    leaderboard = await sdk.get_global_leaderboard_async()
    
    # Check suspicious accounts asynchronously
    suspicious = await sdk.check_suspicious_accounts_async(user_ids=["DISCORD_USER_ID"])
    
    # Get member insights asynchronously
    insights = await sdk.get_member_insights_async(user_ids=["DISCORD_USER_ID"])
    
    # Get invite outcomes asynchronously
    outcomes = await sdk.get_invite_outcomes_async(invite_code="INVITE_CODE")

# Run the async code
asyncio.run(main())
```

## Available Methods

### Synchronous Methods
- `get_custom_quests()`: Get all custom quests for the server
- `get_player_info(discord_user_id)`: Get information about a player
- `complete_custom_quest(custom_quest_id, discord_user_id)`: Mark a custom quest as completed
- `get_completed_members(custom_quest_id)`: Get all members who completed a quest
- `get_global_leaderboard()`: Get global leaderboard for the server
- `check_suspicious_accounts(user_ids)`: Check whether members were flagged as suspicious accounts
- `get_member_insights(user_ids)`: Get activity insights for members
- `get_invite_outcomes(invite_code)`: Get the outcomes recorded for an invite code

### Asynchronous Methods
- `get_custom_quests_async()`: Get all custom quests for the server asynchronously
- `get_player_info_async(discord_user_id)`: Get player information asynchronously
- `complete_custom_quest_async(custom_quest_id, discord_user_id)`: Complete a quest asynchronously
- `get_completed_members_async(custom_quest_id)`: Get completed members asynchronously
- `get_global_leaderboard_async()`: Get global leaderboard asynchronously
- `check_suspicious_accounts_async(user_ids)`: Check suspicious accounts asynchronously
- `get_member_insights_async(user_ids)`: Get member insights asynchronously
- `get_invite_outcomes_async(invite_code)`: Get invite outcomes asynchronously

## Analytics

The analytics methods return point-in-time data and require the server to be on the **Analytics Premium Level 3** tier. Servers on any other tier get a `403`.

### Suspicious accounts

`check_suspicious_accounts(user_ids)` accepts between 1 and 100 Discord user IDs and returns one result per requested ID, in the same order as the request. Requesting more or fewer IDs raises a `ValueError` before the request is sent.

```python
suspicious = sdk.check_suspicious_accounts(user_ids=["1273073873503522888", "851179040428654623"])

for result in suspicious["results"]:
    if result["flagged"]:
        print(result["user_id"], result["flagged_reason"], result["join_date"])
```

A user ID with no suspicious activity is **clean**, not unknown: it comes back with `flagged` set to `False` and every other field set to `None`. `flagged_reason` is an open string (currently `GROUP_JOIN`, `JOIN_RIGHT_AFTER_CREATION`, `RANDOM_USERNAME` or `SCAMLIKE_CHAT`) that may gain new values, and it can be `None` even when `flagged` is `True`.

### Member insights

`get_member_insights(user_ids)` takes the same 1-100 user IDs and returns per-member activity data such as `msg_count`, `days_present`, `wpm` and `most_active_channel_name`.

```python
insights = sdk.get_member_insights(user_ids=["1273073873503522888"])

for result in insights["results"]:
    if result["found"]:
        print(result["username"], result["msg_count"], result["most_active_channel_name"])
```

Here absence genuinely means "no data available", so check `found` rather than assuming a member is inactive. Requesting unknown IDs is not an error. `member_type` (`MODERATOR` or `REGULAR_MEMBER`) is also an open string.

### Invite outcomes

`get_invite_outcomes(invite_code)` returns the outcomes for a single invite code as a flat object. The invite code is URL-encoded for you.

```python
outcomes = sdk.get_invite_outcomes(invite_code="abcd1234")
print(outcomes["total_users"], outcomes["total_suspicious_users"])
```

An invite code with no recorded outcomes returns a `404`, which surfaces as the usual `requests.HTTPError` (or `aiohttp.ClientResponseError` for the async method). This is an expected outcome rather than a fault, so handle it explicitly if you may query codes that were never used:

```python
import requests

try:
    outcomes = sdk.get_invite_outcomes(invite_code="abcd1234")
except requests.HTTPError as error:
    if error.response.status_code == 404:
        outcomes = None
    else:
        raise
```

Invite codes are only unique within a server, since a released vanity code can later be claimed by a different server, so scope any cached results by server ID.

### Discord IDs

Discord IDs are sent and returned as strings to avoid precision loss. `int` values are accepted and coerced with `str()`. The `user_id` echoed back in a response is numerically normalized, so an input of `"0007"` returns as `"7"` — match results to inputs by array position, or normalize your inputs first.

## Testing Mode

CommunityOne allows you to test the full quest completion workflow in your application without affecting production quests data, helping you verify quest functionality before releasing it to your community. When a quest is in testing mode:
- The quest won't be visible to regular Discord server members
- No code changes needed! - use the same SDK methods for testing and production quests (the API automatically routes to our internal test environment)

**How to enable:**
1. Go to your server's [CommunityOne dashboard](https://communityone.io/dashboard)
2. Navigate to Hype Engine > Custom Quests
3. Click the Edit button on your quest
4. Enable testing mode

## Rate Limiting

All API endpoints are subject to rate limiting:
- 60 requests per minute per server for the quest and leaderboard endpoints
- 120 requests per minute per server for the analytics endpoints
- Rate limits are applied separately for each endpoint
- Exceeding the rate limit will result in a 429 Too Many Requests response

## Requirements

- Python 3.7 or higher
- requests>=2.25.0
- aiohttp>=3.8.0

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 
