From ebe8389cb38b2fe494234ba44d7ea5ddb89f3a4a Mon Sep 17 00:00:00 2001 From: oriol Date: Sat, 10 Jan 2026 09:45:35 +0000 Subject: [PATCH] add lib folder --- lib/python/channels.py | 124 ++++++++++++++++++++++++++++ lib/rust/Cargo.toml | 10 +++ lib/rust/src/lib.rs | 164 +++++++++++++++++++++++++++++++++++++ lib/sh/channels.sh | 124 ++++++++++++++++++++++++++++ lib/typescript/channels.ts | 124 ++++++++++++++++++++++++++++ 5 files changed, 546 insertions(+) create mode 100644 lib/python/channels.py create mode 100644 lib/rust/Cargo.toml create mode 100644 lib/rust/src/lib.rs create mode 100644 lib/sh/channels.sh create mode 100644 lib/typescript/channels.ts diff --git a/lib/python/channels.py b/lib/python/channels.py new file mode 100644 index 0000000..a1e6853 --- /dev/null +++ b/lib/python/channels.py @@ -0,0 +1,124 @@ +""" +Channel messaging bindings for Python +Provides functions to list channels, read messages, and send messages by channel name +""" + +import requests +from typing import Optional +from dataclasses import dataclass + + +@dataclass +class Channel: + id: str + name: str + data: dict = None + + @classmethod + def from_dict(cls, d: dict) -> "Channel": + return cls(id=d["id"], name=d["name"], data=d) + + +@dataclass +class Message: + id: str + content: str + channel_id: str + timestamp: Optional[str] = None + author: Optional[str] = None + data: dict = None + + @classmethod + def from_dict(cls, d: dict) -> "Message": + return cls( + id=d["id"], + content=d["content"], + channel_id=d.get("channelId", d.get("channel_id", "")), + timestamp=d.get("timestamp"), + author=d.get("author"), + data=d, + ) + + +class ChannelClient: + def __init__(self, url: str, token: str): + self.url = url.rstrip("/") + self.token = token + self.session = requests.Session() + self.session.headers.update({ + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + }) + + def _request(self, method: str, endpoint: str, **kwargs) -> dict: + response = self.session.request(method, f"{self.url}{endpoint}", **kwargs) + response.raise_for_status() + return response.json() + + def list_channels(self) -> list[Channel]: + """List all available channels""" + data = self._request("GET", "/channels") + return [Channel.from_dict(c) for c in data] + + def find_channel_id_by_name(self, name: str) -> Optional[str]: + """Find a channel ID by its name""" + channels = self.list_channels() + for channel in channels: + if channel.name == name: + return channel.id + return None + + def read_channel(self, name: str) -> Optional[Channel]: + """Read channel details by name""" + channels = self.list_channels() + for channel in channels: + if channel.name == name: + return channel + return None + + def read_messages(self, channel_name: str, limit: Optional[int] = None) -> list[Message]: + """Read messages from a channel by name""" + channel_id = self.find_channel_id_by_name(channel_name) + if not channel_id: + raise ValueError(f"Channel not found: {channel_name}") + + params = {} + if limit: + params["limit"] = limit + + data = self._request("GET", f"/channels/{channel_id}/messages", params=params) + return [Message.from_dict(m) for m in data] + + def send_message(self, channel_name: str, content: str) -> Message: + """Send a message to a channel by name""" + channel_id = self.find_channel_id_by_name(channel_name) + if not channel_id: + raise ValueError(f"Channel not found: {channel_name}") + + data = self._request("POST", f"/channels/{channel_id}/messages", json={"content": content}) + return Message.from_dict(data) + + +def create_client(url: str, token: str) -> ChannelClient: + """Create a new channel client""" + return ChannelClient(url, token) + + +def list_channels(url: str, token: str) -> list[Channel]: + """List all available channels""" + return create_client(url, token).list_channels() + + +def read_channel(url: str, token: str, name: str) -> Optional[Channel]: + """Read channel details by name""" + return create_client(url, token).read_channel(name) + + +def read_messages(url: str, token: str, channel_name: str, limit: Optional[int] = None) -> list[Message]: + """Read messages from a channel by name""" + return create_client(url, token).read_messages(channel_name, limit) + + +def send_message(url: str, token: str, channel_name: str, content: str) -> Message: + """Send a message to a channel by name""" + return create_client(url, token).send_message(channel_name, content) diff --git a/lib/rust/Cargo.toml b/lib/rust/Cargo.toml new file mode 100644 index 0000000..42ea514 --- /dev/null +++ b/lib/rust/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "channels" +version = "0.1.0" +edition = "2021" + +[dependencies] +reqwest = { version = "0.11", features = ["json", "blocking"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0" diff --git a/lib/rust/src/lib.rs b/lib/rust/src/lib.rs new file mode 100644 index 0000000..0342ac4 --- /dev/null +++ b/lib/rust/src/lib.rs @@ -0,0 +1,164 @@ +//! Channel messaging bindings for Rust +//! Provides functions to list channels, read messages, and send messages by channel name + +use reqwest::blocking::Client; +use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ChannelError { + #[error("HTTP request failed: {0}")] + RequestError(#[from] reqwest::Error), + #[error("Channel not found: {0}")] + ChannelNotFound(String), + #[error("Invalid header value")] + InvalidHeader, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Channel { + pub id: String, + pub name: String, + #[serde(flatten)] + pub extra: serde_json::Value, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Message { + pub id: String, + pub content: String, + #[serde(alias = "channelId", alias = "channel_id")] + pub channel_id: String, + pub timestamp: Option, + pub author: Option, + #[serde(flatten)] + pub extra: serde_json::Value, +} + +#[derive(Debug, Serialize)] +struct SendMessagePayload { + content: String, +} + +pub struct ChannelClient { + url: String, + client: Client, +} + +impl ChannelClient { + pub fn new(url: &str, token: &str) -> Result { + let mut headers = HeaderMap::new(); + headers.insert( + AUTHORIZATION, + HeaderValue::from_str(&format!("Bearer {}", token)) + .map_err(|_| ChannelError::InvalidHeader)?, + ); + headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + + let client = Client::builder().default_headers(headers).build()?; + + Ok(Self { + url: url.trim_end_matches('/').to_string(), + client, + }) + } + + /// List all available channels + pub fn list_channels(&self) -> Result, ChannelError> { + let response = self + .client + .get(format!("{}/channels", self.url)) + .send()? + .error_for_status()?; + + Ok(response.json()?) + } + + /// Find a channel ID by its name + pub fn find_channel_id_by_name(&self, name: &str) -> Result, ChannelError> { + let channels = self.list_channels()?; + Ok(channels.into_iter().find(|c| c.name == name).map(|c| c.id)) + } + + /// Read channel details by name + pub fn read_channel(&self, name: &str) -> Result, ChannelError> { + let channels = self.list_channels()?; + Ok(channels.into_iter().find(|c| c.name == name)) + } + + /// Read messages from a channel by name + pub fn read_messages( + &self, + channel_name: &str, + limit: Option, + ) -> Result, ChannelError> { + let channel_id = self + .find_channel_id_by_name(channel_name)? + .ok_or_else(|| ChannelError::ChannelNotFound(channel_name.to_string()))?; + + let mut url = format!("{}/channels/{}/messages", self.url, channel_id); + if let Some(limit) = limit { + url.push_str(&format!("?limit={}", limit)); + } + + let response = self.client.get(&url).send()?.error_for_status()?; + + Ok(response.json()?) + } + + /// Send a message to a channel by name + pub fn send_message(&self, channel_name: &str, content: &str) -> Result { + let channel_id = self + .find_channel_id_by_name(channel_name)? + .ok_or_else(|| ChannelError::ChannelNotFound(channel_name.to_string()))?; + + let payload = SendMessagePayload { + content: content.to_string(), + }; + + let response = self + .client + .post(format!("{}/channels/{}/messages", self.url, channel_id)) + .json(&payload) + .send()? + .error_for_status()?; + + Ok(response.json()?) + } +} + +/// Create a new channel client +pub fn create_client(url: &str, token: &str) -> Result { + ChannelClient::new(url, token) +} + +/// List all available channels +pub fn list_channels(url: &str, token: &str) -> Result, ChannelError> { + create_client(url, token)?.list_channels() +} + +/// Read channel details by name +pub fn read_channel(url: &str, token: &str, name: &str) -> Result, ChannelError> { + create_client(url, token)?.read_channel(name) +} + +/// Read messages from a channel by name +pub fn read_messages( + url: &str, + token: &str, + channel_name: &str, + limit: Option, +) -> Result, ChannelError> { + create_client(url, token)?.read_messages(channel_name, limit) +} + +/// Send a message to a channel by name +pub fn send_message( + url: &str, + token: &str, + channel_name: &str, + content: &str, +) -> Result { + create_client(url, token)?.send_message(channel_name, content) +} diff --git a/lib/sh/channels.sh b/lib/sh/channels.sh new file mode 100644 index 0000000..943e114 --- /dev/null +++ b/lib/sh/channels.sh @@ -0,0 +1,124 @@ +#!/bin/bash +# Channel messaging bindings for Shell +# Provides functions to list channels, read messages, and send messages by channel name +# +# Usage: source this file to use functions, or run directly with commands +# source channels.sh +# list_channels "https://api.example.com" "your-token" +# read_channel "https://api.example.com" "your-token" "channel-name" +# read_messages "https://api.example.com" "your-token" "channel-name" [limit] +# send_message "https://api.example.com" "your-token" "channel-name" "message content" + +set -e + +# List all available channels +# Args: $1 = url, $2 = token +list_channels() { + local url="${1%/}" + local token="$2" + + curl -s -X GET "${url}/channels" \ + -H "Authorization: Bearer ${token}" \ + -H "Content-Type: application/json" +} + +# Find channel ID by name +# Args: $1 = url, $2 = token, $3 = channel_name +# Returns: channel ID or empty string +find_channel_id_by_name() { + local url="$1" + local token="$2" + local channel_name="$3" + + list_channels "$url" "$token" | jq -r --arg name "$channel_name" '.[] | select(.name == $name) | .id' +} + +# Read channel details by name +# Args: $1 = url, $2 = token, $3 = channel_name +read_channel() { + local url="$1" + local token="$2" + local channel_name="$3" + + list_channels "$url" "$token" | jq --arg name "$channel_name" '.[] | select(.name == $name)' +} + +# Read messages from a channel by name +# Args: $1 = url, $2 = token, $3 = channel_name, $4 = limit (optional) +read_messages() { + local url="${1%/}" + local token="$2" + local channel_name="$3" + local limit="$4" + + local channel_id + channel_id=$(find_channel_id_by_name "$url" "$token" "$channel_name") + + if [ -z "$channel_id" ]; then + echo "Error: Channel not found: $channel_name" >&2 + return 1 + fi + + local endpoint="${url}/channels/${channel_id}/messages" + if [ -n "$limit" ]; then + endpoint="${endpoint}?limit=${limit}" + fi + + curl -s -X GET "$endpoint" \ + -H "Authorization: Bearer ${token}" \ + -H "Content-Type: application/json" +} + +# Send a message to a channel by name +# Args: $1 = url, $2 = token, $3 = channel_name, $4 = content +send_message() { + local url="${1%/}" + local token="$2" + local channel_name="$3" + local content="$4" + + local channel_id + channel_id=$(find_channel_id_by_name "$url" "$token" "$channel_name") + + if [ -z "$channel_id" ]; then + echo "Error: Channel not found: $channel_name" >&2 + return 1 + fi + + curl -s -X POST "${url}/channels/${channel_id}/messages" \ + -H "Authorization: Bearer ${token}" \ + -H "Content-Type: application/json" \ + -d "$(jq -n --arg content "$content" '{content: $content}')" +} + +# CLI mode - if script is run directly (not sourced) +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + case "$1" in + list_channels) + shift + list_channels "$@" + ;; + read_channel) + shift + read_channel "$@" + ;; + read_messages) + shift + read_messages "$@" + ;; + send_message) + shift + send_message "$@" + ;; + *) + echo "Usage: $0 {list_channels|read_channel|read_messages|send_message} [args...]" + echo "" + echo "Commands:" + echo " list_channels " + echo " read_channel " + echo " read_messages [limit]" + echo " send_message " + exit 1 + ;; + esac +fi diff --git a/lib/typescript/channels.ts b/lib/typescript/channels.ts new file mode 100644 index 0000000..21dd2a8 --- /dev/null +++ b/lib/typescript/channels.ts @@ -0,0 +1,124 @@ +/** + * Channel messaging bindings for TypeScript + * Provides functions to list channels, read messages, and send messages by channel name + */ + +export interface Channel { + id: string; + name: string; + [key: string]: unknown; +} + +export interface Message { + id: string; + content: string; + channelId: string; + timestamp?: string; + author?: string; + [key: string]: unknown; +} + +export interface ChannelClientConfig { + url: string; + token: string; +} + +export class ChannelClient { + private url: string; + private token: string; + + constructor(config: ChannelClientConfig) { + this.url = config.url.replace(/\/$/, ''); + this.token = config.token; + } + + private async request(endpoint: string, options: RequestInit = {}): Promise { + const response = await fetch(`${this.url}${endpoint}`, { + ...options, + headers: { + 'Authorization': `Bearer ${this.token}`, + 'Content-Type': 'application/json', + ...options.headers, + }, + }); + + if (!response.ok) { + throw new Error(`Request failed: ${response.status} ${response.statusText}`); + } + + return response.json(); + } + + /** + * List all available channels + */ + async listChannels(): Promise { + return this.request('/channels'); + } + + /** + * Find a channel ID by its name + */ + async findChannelIdByName(name: string): Promise { + const channels = await this.listChannels(); + const channel = channels.find(c => c.name === name); + return channel?.id ?? null; + } + + /** + * Read channel details by name + */ + async readChannel(name: string): Promise { + const channels = await this.listChannels(); + return channels.find(c => c.name === name) ?? null; + } + + /** + * Read messages from a channel by name + */ + async readMessages(channelName: string, limit?: number): Promise { + const channelId = await this.findChannelIdByName(channelName); + if (!channelId) { + throw new Error(`Channel not found: ${channelName}`); + } + + const query = limit ? `?limit=${limit}` : ''; + return this.request(`/channels/${channelId}/messages${query}`); + } + + /** + * Send a message to a channel by name + */ + async sendMessage(channelName: string, content: string): Promise { + const channelId = await this.findChannelIdByName(channelName); + if (!channelId) { + throw new Error(`Channel not found: ${channelName}`); + } + + return this.request(`/channels/${channelId}/messages`, { + method: 'POST', + body: JSON.stringify({ content }), + }); + } +} + +// Convenience functions for standalone usage +export function createClient(url: string, token: string): ChannelClient { + return new ChannelClient({ url, token }); +} + +export async function listChannels(url: string, token: string): Promise { + return createClient(url, token).listChannels(); +} + +export async function readChannel(url: string, token: string, name: string): Promise { + return createClient(url, token).readChannel(name); +} + +export async function readMessages(url: string, token: string, channelName: string, limit?: number): Promise { + return createClient(url, token).readMessages(channelName, limit); +} + +export async function sendMessage(url: string, token: string, channelName: string, content: string): Promise { + return createClient(url, token).sendMessage(channelName, content); +}