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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# [4.0.6]

## Added

- **All bindings:** `ContentContext` adds two new methods (Rust, Go, C, C++, Java, Python, Node.js):
- `topics_mine(opts)` — get topics created by the current authenticated user, with optional page/size/topic_type filtering.
- `create_topic(opts)` — create a new topic; returns the full `OwnedTopic` on success.
- **All bindings:** New types `OwnedTopic`, `ListMyTopicsOptions`, and `CreateTopicOptions` to support the above methods.

# [4.0.5]

## Changed
Expand Down
6 changes: 6 additions & 0 deletions c/cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ cpp_compat = true
"CMarketTemperature" = "lb_market_temperature_t"
"CHistoryMarketTemperatureResponse" = "lb_history_market_temperature_response_t"
"CFilingItem" = "lb_filing_item_t"
"CTopicAuthor" = "lb_topic_author_t"
"CTopicImage" = "lb_topic_image_t"
"COwnedTopic" = "lb_owned_topic_t"
"CTopicItem" = "lb_topic_item_t"
"CNewsItem" = "lb_news_item_t"
"CContentContext" = "lb_content_context_t"
Expand Down Expand Up @@ -174,6 +177,9 @@ include = [
"CMarketTemperature",
"CHistoryMarketTemperatureResponse",
"CFilingItem",
"CTopicAuthor",
"CTopicImage",
"COwnedTopic",
"CTopicItem",
"CNewsItem",
"COAuth",
Expand Down
166 changes: 166 additions & 0 deletions c/csrc/include/longbridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -3909,6 +3909,128 @@ typedef struct lb_filing_item_t {
int64_t published_at;
} lb_filing_item_t;

/**
* Topic author
*/
typedef struct lb_topic_author_t {
/**
* Member ID
*/
const char *member_id;
/**
* Display name
*/
const char *name;
/**
* Avatar URL
*/
const char *avatar;
} lb_topic_author_t;

/**
* Topic image
*/
typedef struct lb_topic_image_t {
/**
* Original image URL
*/
const char *url;
/**
* Small thumbnail URL
*/
const char *sm;
/**
* Large image URL
*/
const char *lg;
} lb_topic_image_t;

/**
* My topic item (topic created by the current authenticated user)
*/
typedef struct lb_owned_topic_t {
/**
* Topic ID
*/
const char *id;
/**
* Title
*/
const char *title;
/**
* Plain text excerpt
*/
const char *description;
/**
* Markdown body
*/
const char *body;
/**
* Author
*/
struct lb_topic_author_t author;
/**
* Related stock tickers
*/
const char *const *tickers;
/**
* Number of tickers
*/
uintptr_t num_tickers;
/**
* Hashtag names
*/
const char *const *hashtags;
/**
* Number of hashtags
*/
uintptr_t num_hashtags;
/**
* Images
*/
const struct lb_topic_image_t *images;
/**
* Number of images
*/
uintptr_t num_images;
/**
* Likes count
*/
int32_t likes_count;
/**
* Comments count
*/
int32_t comments_count;
/**
* Views count
*/
int32_t views_count;
/**
* Shares count
*/
int32_t shares_count;
/**
* Content type: "article" or "post"
*/
const char *topic_type;
/**
* License: 0=none, 1=original, 2=non-original
*/
int32_t license;
/**
* URL to the full topic page
*/
const char *detail_url;
/**
* Created time (Unix timestamp)
*/
int64_t created_at;
/**
* Updated time (Unix timestamp)
*/
int64_t updated_at;
} lb_owned_topic_t;

/**
* Topic item
*/
Expand Down Expand Up @@ -4123,6 +4245,50 @@ void lb_content_context_retain(const struct lb_content_context_t *ctx);
*/
void lb_content_context_release(const struct lb_content_context_t *ctx);

/**
* Get topics created by the current authenticated user
*
* @param ctx Content context
* @param page Page number (0 = default 1)
* @param size Records per page, range 1~500 (0 = default 50)
* @param topic_type Filter by content type: "article" or "post" (NULL = all)
* @param callback Async callback
* @param userdata User data passed to the callback
*/
void lb_content_context_topics_mine(const struct lb_content_context_t *ctx,
int32_t page,
int32_t size,
const char *topic_type,
lb_async_callback_t callback,
void *userdata);

/**
* Create a new topic
*
* @param ctx Content context
* @param title Topic title (required)
* @param body Topic body in Markdown format (required)
* @param topic_type Type: "article" or "post" (NULL = "post")
* @param tickers Related stock tickers array (NULL = none)
* @param num_tickers Number of tickers
* @param hashtags Hashtag names array (NULL = none)
* @param num_hashtags Number of hashtags
* @param license 0=none, 1=original, 2=non-original (-1 = default)
* @param callback Async callback
* @param userdata User data passed to the callback
*/
void lb_content_context_create_topic(const struct lb_content_context_t *ctx,
const char *title,
const char *body,
const char *topic_type,
const char *const *tickers,
uintptr_t num_tickers,
const char *const *hashtags,
uintptr_t num_hashtags,
int32_t license,
lb_async_callback_t callback,
void *userdata);

/**
* Get discussion topics list for a symbol
*
Expand Down
104 changes: 101 additions & 3 deletions c/src/content_context/context.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::{ffi::c_void, os::raw::c_char, sync::Arc};

use longbridge::content::ContentContext;
use longbridge::content::{ContentContext, CreateTopicOptions, ListMyTopicsOptions};

use crate::{
async_call::{CAsyncCallback, execute_async},
config::CConfig,
content_context::types::{CNewsItemOwned, CTopicItemOwned},
types::{CVec, cstr_to_rust},
content_context::types::{CNewsItemOwned, COwnedTopicOwned, CTopicItemOwned},
types::{CVec, cstr_array_to_rust, cstr_to_rust},
};

/// Content context
Expand Down Expand Up @@ -37,6 +37,104 @@ pub unsafe extern "C" fn lb_content_context_release(ctx: *const CContentContext)
let _ = Arc::from_raw(ctx);
}

/// Get topics created by the current authenticated user
///
/// @param ctx Content context
/// @param page Page number (0 = default 1)
/// @param size Records per page, range 1~500 (0 = default 50)
/// @param topic_type Filter by content type: "article" or "post" (NULL = all)
/// @param callback Async callback
/// @param userdata User data passed to the callback
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lb_content_context_topics_mine(
ctx: *const CContentContext,
page: i32,
size: i32,
topic_type: *const c_char,
callback: CAsyncCallback,
userdata: *mut c_void,
) {
let ctx_inner = (*ctx).ctx.clone();
let topic_type = if topic_type.is_null() {
None
} else {
Some(cstr_to_rust(topic_type))
};
execute_async(callback, ctx, userdata, async move {
let rows: CVec<COwnedTopicOwned> = ctx_inner
.topics_mine(ListMyTopicsOptions {
page: if page > 0 { Some(page) } else { None },
size: if size > 0 { Some(size) } else { None },
topic_type,
})
.await?
.into();
Ok(rows)
});
}

/// Create a new topic
///
/// @param ctx Content context
/// @param title Topic title (required)
/// @param body Topic body in Markdown format (required)
/// @param topic_type Type: "article" or "post" (NULL = "post")
/// @param tickers Related stock tickers array (NULL = none)
/// @param num_tickers Number of tickers
/// @param hashtags Hashtag names array (NULL = none)
/// @param num_hashtags Number of hashtags
/// @param license 0=none, 1=original, 2=non-original (-1 = default)
/// @param callback Async callback
/// @param userdata User data passed to the callback
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lb_content_context_create_topic(
ctx: *const CContentContext,
title: *const c_char,
body: *const c_char,
topic_type: *const c_char,
tickers: *const *const c_char,
num_tickers: usize,
hashtags: *const *const c_char,
num_hashtags: usize,
license: i32,
callback: CAsyncCallback,
userdata: *mut c_void,
) {
let ctx_inner = (*ctx).ctx.clone();
let title = cstr_to_rust(title);
let body = cstr_to_rust(body);
let topic_type = if topic_type.is_null() {
None
} else {
Some(cstr_to_rust(topic_type))
};
let tickers = if tickers.is_null() || num_tickers == 0 {
None
} else {
Some(cstr_array_to_rust(tickers, num_tickers))
};
let hashtags = if hashtags.is_null() || num_hashtags == 0 {
None
} else {
Some(cstr_array_to_rust(hashtags, num_hashtags))
};
let license = if license >= 0 { Some(license) } else { None };
execute_async(callback, ctx, userdata, async move {
let owned = ctx_inner
.create_topic(CreateTopicOptions {
title,
body,
topic_type,
tickers,
hashtags,
license,
})
.await?;
let rows: CVec<COwnedTopicOwned> = vec![COwnedTopicOwned::from(owned)].into();
Ok(rows)
});
}

/// Get discussion topics list for a symbol
///
/// @param ctx Content context
Expand Down
Loading
Loading