From b4aa16bfc7f8fe241969cdebb1521c2a927a9360 Mon Sep 17 00:00:00 2001 From: Dylan Morley Date: Mon, 26 Aug 2024 18:56:43 +0100 Subject: [PATCH 1/4] Added collaboration value to ContentTypes --- src/Dapplo.Confluence/Query/ContentTypes.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Dapplo.Confluence/Query/ContentTypes.cs b/src/Dapplo.Confluence/Query/ContentTypes.cs index c1c4d60..800a09d 100644 --- a/src/Dapplo.Confluence/Query/ContentTypes.cs +++ b/src/Dapplo.Confluence/Query/ContentTypes.cs @@ -34,5 +34,9 @@ public enum ContentTypes /// /// The content is a personal page /// - [EnumMember(Value = "personal")] Personal + [EnumMember(Value = "personal")] Personal, + /// + /// The content is details about collaboration + /// + [EnumMember(Value = "collaboration")] Collaboration } \ No newline at end of file From a29fb9ad286a2e0934a82c409f20fcb1cd0ea52c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 10:11:16 +0000 Subject: [PATCH 2/4] Initial plan From daa09c08442d5668d027cb0db8571d4d424edcd9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 10:14:56 +0000 Subject: [PATCH 3/4] Update README with comprehensive project documentation Co-authored-by: dylan-asos <16137664+dylan-asos@users.noreply.github.com> --- README.md | 405 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 386 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index d594709..2ba5939 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,399 @@ # Dapplo.Confluence -This is a simple REST based Confluence client, written for Greenshot, by using Dapplo.HttpExtension -- Current build status: [![Build Status](https://dev.azure.com/Dapplo/Dapplo%20framework/_apis/build/status/dapplo.Dapplo.Confluence?branchName=master)](https://dev.azure.com/Dapplo/Dapplo%20framework/_build/latest?definitionId=11&branchName=master) -- Coverage Status: [![Coverage Status](https://coveralls.io/repos/github/dapplo/Dapplo.Confluence/badge.svg?branch=master)](https://coveralls.io/github/dapplo/Dapplo.Confluence?branch=master) -- NuGet package: [![NuGet package](https://badge.fury.io/nu/Dapplo.Confluence.svg)](https://badge.fury.io/nu/Dapplo.Confluence) +[![Build Status](https://dev.azure.com/Dapplo/Dapplo%20framework/_apis/build/status/dapplo.Dapplo.Confluence?branchName=master)](https://dev.azure.com/Dapplo/Dapplo%20framework/_build/latest?definitionId=11&branchName=master) +[![Coverage Status](https://coveralls.io/repos/github/dapplo/Dapplo.Confluence/badge.svg?branch=master)](https://coveralls.io/github/dapplo/Dapplo.Confluence?branch=master) +[![NuGet package](https://badge.fury.io/nu/Dapplo.Confluence.svg)](https://badge.fury.io/nu/Dapplo.Confluence) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -The Confluence client supports most REST methods, and has a fluent API for building a CQL (Confluence Query Language) string to search with. +## Overview -An example on how to use this Confluence client: +**Dapplo.Confluence** is a .NET client library for interacting with Atlassian Confluence via its REST API. Originally developed for [Greenshot](https://getgreenshot.org/), this library provides a clean, intuitive interface for programmatically managing Confluence content, spaces, users, attachments, and more. + +Built on top of [Dapplo.HttpExtensions](https://github.com/dapplo/Dapplo.HttpExtensions), it offers a fluent API for building CQL (Confluence Query Language) queries and supports both Confluence Server and Confluence Cloud deployments. + +## Key Features + +- **Full REST API Support**: Interact with Content, Spaces, Users, Attachments, Groups, and more +- **Fluent CQL Query Builder**: Build type-safe Confluence Query Language queries with IntelliSense support +- **Multiple Authentication Methods**: Basic Authentication, Bearer Token (PAT), and OAuth support +- **Multi-Target Framework Support**: Works with .NET Standard 1.3/2.0, .NET Framework 4.7.1, .NET Core 3.1, and .NET 6.0 +- **Async/Await Pattern**: All API operations are fully asynchronous +- **Extensible Plugin System**: Easily extend the client with custom functionality +- **Cloud & Server Compatible**: Works with both Confluence Cloud and Confluence Server/Data Center + +## Installation + +Install via NuGet Package Manager: + +```bash +dotnet add package Dapplo.Confluence ``` -var confluenceClient = ConfluenceClient.Create(new Uri("https://confluence")); -confluenceClient.SetBasicAuthentication(username, password); + +Or via the Package Manager Console: + +```powershell +Install-Package Dapplo.Confluence +``` + +For OAuth authentication support, also install: + +```bash +dotnet add package Dapplo.Confluence.OAuth +``` + +## Prerequisites + +- .NET Standard 1.3+ compatible runtime, or +- .NET Framework 4.7.1+, or +- .NET Core 3.1+, or +- .NET 6.0+ + +## Quick Start + +### Creating a Client + +```csharp +using Dapplo.Confluence; + +// Create a client instance +var confluenceClient = ConfluenceClient.Create(new Uri("https://your-confluence-url")); +``` + +### Authentication + +**Basic Authentication (Confluence Server):** +```csharp +confluenceClient.SetBasicAuthentication("username", "password"); +``` + +**Basic Authentication (Confluence Cloud):** +```csharp +// For Confluence Cloud, use your email as username and an API token as password +// Generate API token at: https://id.atlassian.com/manage/api-tokens +confluenceClient.SetBasicAuthentication("your-email@example.com", "your-api-token"); +``` + +**Bearer Token / Personal Access Token:** +```csharp +confluenceClient.SetBearerAuthentication("your-personal-access-token"); +``` + +### Basic Usage Example + +```csharp +using Dapplo.Confluence; +using Dapplo.Confluence.Query; + +// Create and authenticate client +var confluenceClient = ConfluenceClient.Create(new Uri("https://your-domain.atlassian.net/wiki")); +confluenceClient.SetBasicAuthentication("email@example.com", "api-token"); + +// Build a CQL query var query = Where.And(Where.Type.IsPage, Where.Text.Contains("Test Home")); -var searchResult = await confluenceClient.Content.SearchAsync(query, limit:1); + +// Search for content +var searchResult = await confluenceClient.Content.SearchAsync(query, limit: 10); + foreach (var contentDigest in searchResult.Results) { - // As the content from the Search is a digest, get the details (it's also possible to get the details during the search) - var content = await confluenceClient.Content.GetAsync(contentDigest, ConfluenceClientConfig.ExpandGetContentWithStorage); - // Output the information - Debug.WriteLine(content.Body); + // Get full content details with body storage format + var content = await confluenceClient.Content.GetAsync( + contentDigest, + ConfluenceClientConfig.ExpandGetContentWithStorage + ); + + Console.WriteLine($"Title: {content.Title}"); + Console.WriteLine($"Body: {content.Body.Storage.Value}"); } ``` -If you want to extend the API for a specific use-case where it doesn't make sense to provide it to the rest of the world via a pull-request, for example to add logic for a *plugin*, you can write an extension method to extend the IConfluenceClientPlugins. -Your "plugin" extension will now be available, if the developer has a using statement of your namespace, on the .Plugins property of the IConfluenceClient +## API Domains + +The client is organized into logical domains for different Confluence operations: + +### Content Domain (`confluenceClient.Content`) +- Create, read, update, and delete pages and blog posts +- Search content using CQL queries +- Manage content labels +- Get content history and children +- Copy and move content (Cloud only) + +```csharp +// Create a new page +var page = await confluenceClient.Content.CreateAsync( + ContentTypes.Page, + "Page Title", + "SPACEKEY", + "

Page content in HTML

" +); + +// Get content by ID +var content = await confluenceClient.Content.GetAsync(contentId); + +// Update existing content +content.Body.Storage.Value = "

Updated content

"; +content.Version.Number++; +await confluenceClient.Content.UpdateAsync(content); + +// Delete content +await confluenceClient.Content.DeleteAsync(contentId); +``` + +### Space Domain (`confluenceClient.Space`) +- Get space information and list spaces +- Access space contents + +```csharp +// Get all spaces +var spaces = await confluenceClient.Space.GetAllAsync(); + +// Get a specific space +var space = await confluenceClient.Space.GetAsync("SPACEKEY"); +``` + +### User Domain (`confluenceClient.User`) +- Get user information +- Search for users + +```csharp +// Get current user +var currentUser = await confluenceClient.User.GetCurrentUserAsync(); + +// Get user by username +var user = await confluenceClient.User.GetAsync("username"); +``` + +### Attachment Domain (`confluenceClient.Attachment`) +- Upload, download, and manage attachments + +```csharp +// Get attachments for content +var attachments = await confluenceClient.Attachment.GetAttachmentsAsync(contentId); + +// Download attachment content +var attachmentData = await confluenceClient.Attachment.GetContentAsync(attachment); +``` + +### Group Domain (`confluenceClient.Group`) +- Get group information and members + +```csharp +// Get all groups +var groups = await confluenceClient.Group.GetGroupsAsync(); + +// Get group members +var members = await confluenceClient.Group.GetMembersAsync("group-name"); +``` + +### Misc Domain (`confluenceClient.Misc`) +- Get system information +- Access other utility endpoints + +```csharp +// Get system information +var systemInfo = await confluenceClient.Misc.GetSystemInfoAsync(); + +// Check if connected to cloud server +var isCloud = await confluenceClient.IsCloudServer(); +``` + +## CQL Query Builder + +The library includes a fluent API for building CQL (Confluence Query Language) queries: + +```csharp +using Dapplo.Confluence.Query; + +// Simple queries +var pageQuery = Where.Type.IsPage; +var blogQuery = Where.Type.IsBlogpost; + +// Combined queries with AND +var query = Where.And( + Where.Space.Is("DEV"), + Where.Type.IsPage, + Where.Title.Contains("documentation") +); + +// Combined queries with OR +var orQuery = Where.Or( + Where.Creator.Is("john.doe"), + Where.Contributor.Is("john.doe") +); + +// Date-based queries +var recentQuery = Where.LastModified.After(DateTime.Now.AddDays(-7)); +var createdQuery = Where.Created.Before(DateTime.Now.AddMonths(-1)); + +// Label queries +var labelQuery = Where.Label.Is("important"); + +// Text search +var textQuery = Where.Text.Contains("API documentation"); + +// Complex nested queries +var complexQuery = Where.And( + Where.Space.Is("DEV"), + Where.Type.IsPage, + Where.Or( + Where.Title.Contains("guide"), + Where.Label.Is("tutorial") + ) +); +``` + +### Available Query Fields + +| Field | Description | Example | +|-------|-------------|---------| +| `Where.Type` | Content type (page, blogpost, attachment, comment) | `Where.Type.IsPage` | +| `Where.Space` | Space key | `Where.Space.Is("DEV")` | +| `Where.Title` | Content title | `Where.Title.Contains("guide")` | +| `Where.Text` | Full-text search | `Where.Text.Contains("search term")` | +| `Where.Label` | Content labels | `Where.Label.Is("important")` | +| `Where.Creator` | Content creator | `Where.Creator.Is("username")` | +| `Where.Contributor` | Content contributors | `Where.Contributor.Is("username")` | +| `Where.Created` | Creation date | `Where.Created.After(date)` | +| `Where.LastModified` | Last modified date | `Where.LastModified.Before(date)` | +| `Where.Id` | Content ID | `Where.Id.Is(12345)` | +| `Where.Ancestor` | Ancestor content ID | `Where.Ancestor.Is(parentId)` | +| `Where.Parent` | Parent content ID | `Where.Parent.Is(parentId)` | + +## Extending the Client (Plugin System) + +You can extend the Confluence client with custom functionality by creating extension methods on `IConfluenceClientPlugins`: + +```csharp +public static class MyConfluenceExtensions +{ + public static async Task MyCustomOperationAsync( + this IConfluenceClientPlugins plugins, + string parameter) + { + // Your custom implementation + // Access the client via plugins as IConfluenceDomain + var confluenceClient = plugins as IConfluenceDomain; + + // Make custom API calls using the client's behavior + confluenceClient.Behaviour.MakeCurrent(); + + // ... your custom logic + + return result; + } +} +``` + +Usage: +```csharp +// Use your extension via the Plugins property +var result = await confluenceClient.Plugins.MyCustomOperationAsync("param"); +``` + +## Confluence Cloud vs Server + +### URL Differences + +| Type | URL Format | +|------|------------| +| **Server/Data Center** | `https://confluence.yourcompany.com` | +| **Cloud** | `https://your-domain.atlassian.net/wiki` | + +### Authentication Differences + +| Type | Username | Password/Token | +|------|----------|----------------| +| **Server** | Your username | Your password | +| **Cloud** | Your email address | API token from [Atlassian account](https://id.atlassian.com/manage/api-tokens) | + +### Feature Differences + +Some features are only available on Confluence Cloud: +- `Content.MoveAsync()` - Move content to a different location +- `Content.CopyAsync()` - Copy content + +You can check if you're connected to a cloud instance: +```csharp +var isCloud = await confluenceClient.IsCloudServer(); +``` + +## Configuration + +You can customize the default expand parameters for various operations: + +```csharp +// Configure what fields to expand when getting content +ConfluenceClientConfig.ExpandGetContent = new[] { + "version", + "body.storage", + "ancestors" +}; + +// Configure expand for search results +ConfluenceClientConfig.ExpandSearch = new[] { + "version", + "space", + "history.lastUpdated" +}; + +// Use predefined configurations +var content = await confluenceClient.Content.GetAsync( + contentId, + ConfluenceClientConfig.ExpandGetContentWithStorage +); +``` + +## Error Handling + +The library throws `ConfluenceException` for API errors: + +```csharp +try +{ + var content = await confluenceClient.Content.GetAsync(invalidId); +} +catch (ConfluenceException ex) +{ + Console.WriteLine($"Confluence error: {ex.Message}"); + // Access detailed error information if available +} +``` + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change. + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/AmazingFeature`) +3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + +## Building from Source + +```bash +# Clone the repository +git clone https://github.com/dapplo/Dapplo.Confluence.git +cd Dapplo.Confluence + +# Restore dependencies and build +dotnet restore src/Dapplo.Confluence.sln +dotnet build src/Dapplo.Confluence.sln --configuration Release + +# Run tests (requires Confluence test instance configuration) +dotnet test src/Dapplo.Confluence.sln +``` + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +- Originally developed for [Greenshot](https://getgreenshot.org/) +- Built on [Dapplo.HttpExtensions](https://github.com/dapplo/Dapplo.HttpExtensions) -*Hint:* -For Confluence Cloud, the username & password are a bit different than for Confluence Server. -For cloud your username is the email-address you use to login to atlassian and the PW is an API token which needs to be generated here: https://id.atlassian.com/manage/api-tokens +## Related Projects -Also the URL to use for Confluence cloud is most likely something like the following (where {domain} is your domain): https://{domain}.atlassian.net/wiki +- [Dapplo.Jira](https://github.com/dapplo/Dapplo.Jira) - Similar client library for Atlassian Jira +- [Dapplo.HttpExtensions](https://github.com/dapplo/Dapplo.HttpExtensions) - HTTP client extensions library From 6de495b1382f9a71227dce5a44c5474fdffeeaad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 10:16:04 +0000 Subject: [PATCH 4/4] Address code review feedback: add null-safe access and version increment explanation Co-authored-by: dylan-asos <16137664+dylan-asos@users.noreply.github.com> --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ba5939..24c2a3f 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ foreach (var contentDigest in searchResult.Results) ); Console.WriteLine($"Title: {content.Title}"); - Console.WriteLine($"Body: {content.Body.Storage.Value}"); + Console.WriteLine($"Body: {content.Body?.Storage?.Value}"); } ``` @@ -132,6 +132,7 @@ var content = await confluenceClient.Content.GetAsync(contentId); // Update existing content content.Body.Storage.Value = "

Updated content

"; +// Confluence requires incrementing the version number for updates to prevent conflicts content.Version.Number++; await confluenceClient.Content.UpdateAsync(content);