Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions python/src/content/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ use std::sync::Arc;

use longbridge::{
blocking::ContentContextSync,
content::{CreateTopicOptions, MyTopicsOptions},
content::{CreateReplyOptions, CreateTopicOptions, ListTopicRepliesOptions, MyTopicsOptions},
};
use pyo3::prelude::*;

use crate::{
config::Config,
content::types::{NewsItem, OwnedTopic, TopicItem},
content::types::{NewsItem, OwnedTopic, TopicItem, TopicReply},
error::ErrorNewType,
};

Expand Down Expand Up @@ -46,7 +46,9 @@ impl ContentContext {
.collect()
}

/// Create a new topic
/// Create a new community topic.
///
/// See: <https://open.longbridge.com/docs/api?op=create_topic>
#[pyo3(signature = (title, body, topic_type = None, tickers = None, hashtags = None))]
pub fn create_topic(
&self,
Expand Down Expand Up @@ -87,4 +89,41 @@ impl ContentContext {
.map(TryInto::try_into)
.collect()
}

/// Get full details of a topic by its ID
pub fn topic_detail(&self, id: String) -> PyResult<OwnedTopic> {
self.ctx.topic_detail(id).map_err(ErrorNewType)?.try_into()
}

/// List replies on a topic
#[pyo3(signature = (topic_id, page = None, size = None))]
pub fn list_topic_replies(
&self,
topic_id: String,
page: Option<i32>,
size: Option<i32>,
) -> PyResult<Vec<TopicReply>> {
self.ctx
.list_topic_replies(topic_id, ListTopicRepliesOptions { page, size })
.map_err(ErrorNewType)?
.into_iter()
.map(TryInto::try_into)
.collect()
}

/// Post a reply to a community topic.
///
/// See: <https://open.longbridge.com/docs/api?op=create_topic_reply>
#[pyo3(signature = (topic_id, body, reply_to_id = None))]
pub fn create_topic_reply(
&self,
topic_id: String,
body: String,
reply_to_id: Option<String>,
) -> PyResult<TopicReply> {
self.ctx
.create_topic_reply(topic_id, CreateReplyOptions { body, reply_to_id })
.map_err(ErrorNewType)?
.try_into()
}
}
65 changes: 62 additions & 3 deletions python/src/content/context_async.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use std::sync::Arc;

use longbridge::content::{ContentContext, CreateTopicOptions, MyTopicsOptions};
use longbridge::content::{
ContentContext, CreateReplyOptions, CreateTopicOptions, ListTopicRepliesOptions,
MyTopicsOptions,
};
use pyo3::{prelude::*, types::PyType};

use crate::{
config::Config,
content::types::{NewsItem, OwnedTopic, TopicItem},
content::types::{NewsItem, OwnedTopic, TopicItem, TopicReply},
error::ErrorNewType,
};

Expand Down Expand Up @@ -51,7 +54,9 @@ impl AsyncContentContext {
.map(|b| b.unbind())
}

/// Create a new topic. Returns awaitable.
/// Create a new community topic. Returns awaitable.
///
/// See: <https://open.longbridge.com/docs/api?op=create_topic>
#[pyo3(signature = (title, body, topic_type = None, tickers = None, hashtags = None))]
fn create_topic(
&self,
Expand Down Expand Up @@ -101,4 +106,58 @@ impl AsyncContentContext {
})
.map(|b| b.unbind())
}

/// Get full details of a topic by its ID. Returns awaitable.
fn topic_detail(&self, py: Python<'_>, id: String) -> PyResult<Py<PyAny>> {
let ctx = self.ctx.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let v = ctx.topic_detail(id).await.map_err(ErrorNewType)?;
OwnedTopic::try_from(v)
})
.map(|b| b.unbind())
}

/// List replies on a topic. Returns awaitable.
#[pyo3(signature = (topic_id, page = None, size = None))]
fn list_topic_replies(
&self,
py: Python<'_>,
topic_id: String,
page: Option<i32>,
size: Option<i32>,
) -> PyResult<Py<PyAny>> {
let ctx = self.ctx.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let v = ctx
.list_topic_replies(topic_id, ListTopicRepliesOptions { page, size })
.await
.map_err(ErrorNewType)?;
v.into_iter()
.map(|x| -> PyResult<TopicReply> { x.try_into() })
.collect::<PyResult<Vec<TopicReply>>>()
})
.map(|b| b.unbind())
}

/// Post a reply to a community topic. Returns awaitable.
///
/// See: <https://open.longbridge.com/docs/api?op=create_topic_reply>
#[pyo3(signature = (topic_id, body, reply_to_id = None))]
fn create_topic_reply(
&self,
py: Python<'_>,
topic_id: String,
body: String,
reply_to_id: Option<String>,
) -> PyResult<Py<PyAny>> {
let ctx = self.ctx.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let v = ctx
.create_topic_reply(topic_id, CreateReplyOptions { body, reply_to_id })
.await
.map_err(ErrorNewType)?;
TopicReply::try_from(v)
})
.map(|b| b.unbind())
}
}
1 change: 1 addition & 0 deletions python/src/content/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub(crate) fn register_types(parent: &Bound<PyModule>) -> PyResult<()> {
parent.add_class::<types::TopicAuthor>()?;
parent.add_class::<types::TopicImage>()?;
parent.add_class::<types::OwnedTopic>()?;
parent.add_class::<types::TopicReply>()?;
parent.add_class::<context::ContentContext>()?;
parent.add_class::<context_async::AsyncContentContext>()?;
Ok(())
Expand Down
26 changes: 26 additions & 0 deletions python/src/content/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,32 @@ pub(crate) struct TopicItem {
shares_count: i32,
}

/// A reply on a topic
#[pyclass(skip_from_py_object)]
#[derive(Debug, PyObject, Clone)]
#[py(remote = "longbridge::content::TopicReply")]
pub(crate) struct TopicReply {
/// Reply ID
id: String,
/// Topic ID this reply belongs to
topic_id: String,
/// Reply body (plain text)
body: String,
/// ID of the parent reply ("0" means top-level)
reply_to_id: String,
/// Author info
author: TopicAuthor,
/// Attached images
#[py(array)]
images: Vec<TopicImage>,
/// Likes count
likes_count: i32,
/// Nested replies count
comments_count: i32,
/// Created time
created_at: PyOffsetDateTimeWrapper,
}

/// News item
#[pyclass(skip_from_py_object)]
#[derive(Debug, PyObject, Clone)]
Expand Down
32 changes: 31 additions & 1 deletion rust/src/blocking/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use crate::{
Config, Result,
blocking::runtime::BlockingRuntime,
content::{
ContentContext, CreateTopicOptions, MyTopicsOptions, NewsItem, OwnedTopic, TopicItem,
ContentContext, CreateReplyOptions, CreateTopicOptions, ListTopicRepliesOptions,
MyTopicsOptions, NewsItem, OwnedTopic, TopicItem, TopicReply,
},
};

Expand Down Expand Up @@ -55,4 +56,33 @@ impl ContentContextSync {
self.rt
.call(move |ctx| async move { ctx.news(symbol).await })
}

/// Get full details of a topic by its ID
pub fn topic_detail(&self, id: impl Into<String>) -> Result<OwnedTopic> {
let id = id.into();
self.rt
.call(move |ctx| async move { ctx.topic_detail(id).await })
}

/// List replies on a topic
pub fn list_topic_replies(
&self,
topic_id: impl Into<String>,
opts: ListTopicRepliesOptions,
) -> Result<Vec<TopicReply>> {
let topic_id = topic_id.into();
self.rt
.call(move |ctx| async move { ctx.list_topic_replies(topic_id, opts).await })
}

/// Post a reply to a topic
pub fn create_topic_reply(
&self,
topic_id: impl Into<String>,
opts: CreateReplyOptions,
) -> Result<TopicReply> {
let topic_id = topic_id.into();
self.rt
.call(move |ctx| async move { ctx.create_topic_reply(topic_id, opts).await })
}
}
92 changes: 87 additions & 5 deletions rust/src/content/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ use std::sync::Arc;
use longbridge_httpcli::{HttpClient, Json, Method};
use serde::Deserialize;

use super::types::{CreateTopicOptions, MyTopicsOptions, NewsItem, OwnedTopic, TopicItem};
use super::types::{
CreateReplyOptions, CreateTopicOptions, ListTopicRepliesOptions, MyTopicsOptions, NewsItem,
OwnedTopic, TopicItem, TopicReply,
};
use crate::{Config, Result};

struct InnerContentContext {
Expand All @@ -22,9 +25,9 @@ impl ContentContext {
}))
}

/// Get topics created by the current authenticated user
/// Get topics created by the current authenticated user.
///
/// Path: GET /v1/content/topics/mine
/// See: <https://open.longbridge.com/docs/api?op=list_my_topics>
pub async fn my_topics(&self, opts: MyTopicsOptions) -> Result<Vec<OwnedTopic>> {
#[derive(Debug, Deserialize)]
struct Response {
Expand All @@ -43,9 +46,9 @@ impl ContentContext {
.items)
}

/// Create a new topic
/// Create a new community topic.
///
/// Path: POST /v1/content/topics
/// See: <https://open.longbridge.com/docs/api?op=create_topic>
pub async fn create_topic(&self, opts: CreateTopicOptions) -> Result<String> {
#[derive(Debug, Deserialize)]
struct TopicId {
Expand Down Expand Up @@ -89,6 +92,85 @@ impl ContentContext {
.items)
}

/// Get full details of a topic by its ID.
///
/// See: <https://open.longbridge.com/docs/api?op=topic_detail>
pub async fn topic_detail(&self, id: impl Into<String>) -> Result<OwnedTopic> {
#[derive(Debug, Deserialize)]
struct Response {
item: OwnedTopic,
}

let id = id.into();
Ok(self
.0
.http_cli
.request(Method::GET, format!("/v1/content/topics/{id}"))
.response::<Json<Response>>()
.send()
.await?
.0
.item)
}

/// List replies on a topic.
///
/// See: <https://open.longbridge.com/docs/api?op=list_topic_replies>
pub async fn list_topic_replies(
&self,
topic_id: impl Into<String>,
opts: ListTopicRepliesOptions,
) -> Result<Vec<TopicReply>> {
#[derive(Debug, Deserialize)]
struct Response {
items: Vec<TopicReply>,
}

let topic_id = topic_id.into();
Ok(self
.0
.http_cli
.request(
Method::GET,
format!("/v1/content/topics/{topic_id}/comments"),
)
.query_params(opts)
.response::<Json<Response>>()
.send()
.await?
.0
.items)
}

/// Post a reply to a community topic.
///
/// See: <https://open.longbridge.com/docs/api?op=create_topic_reply>
pub async fn create_topic_reply(
&self,
topic_id: impl Into<String>,
opts: CreateReplyOptions,
) -> Result<TopicReply> {
#[derive(Debug, Deserialize)]
struct Response {
item: TopicReply,
}

let topic_id = topic_id.into();
Ok(self
.0
.http_cli
.request(
Method::POST,
format!("/v1/content/topics/{topic_id}/comments"),
)
.body(Json(opts))
.response::<Json<Response>>()
.send()
.await?
.0
.item)
}

/// Get news list
pub async fn news(&self, symbol: impl Into<String>) -> Result<Vec<NewsItem>> {
#[derive(Debug, Deserialize)]
Expand Down
3 changes: 2 additions & 1 deletion rust/src/content/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ mod types;

pub use context::ContentContext;
pub use types::{
CreateTopicOptions, MyTopicsOptions, NewsItem, OwnedTopic, TopicAuthor, TopicImage, TopicItem,
CreateReplyOptions, CreateTopicOptions, ListTopicRepliesOptions, MyTopicsOptions, NewsItem,
OwnedTopic, TopicAuthor, TopicImage, TopicItem, TopicReply,
};
Loading
Loading