{"id":31956823,"url":"https://github.com/nathanborror/swift-irc-generated","last_synced_at":"2025-10-14T14:54:37.009Z","repository":{"id":318561520,"uuid":"1071818847","full_name":"nathanborror/swift-irc-generated","owner":"nathanborror","description":"A generated library, commit messages are the prompts used. Currently using Zed Agent with Sonnet 4.5.","archived":false,"fork":false,"pushed_at":"2025-10-07T21:44:16.000Z","size":45,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-07T23:31:53.626Z","etag":null,"topics":["generated-code","sonnet-4-5","zed"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nathanborror.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-07T21:18:28.000Z","updated_at":"2025-10-07T21:44:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"c90e2ac6-c9d8-4b33-be94-9d1f7533245f","html_url":"https://github.com/nathanborror/swift-irc-generated","commit_stats":null,"previous_names":["nathanborror/swift-irc-generated"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/nathanborror/swift-irc-generated","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nathanborror%2Fswift-irc-generated","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nathanborror%2Fswift-irc-generated/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nathanborror%2Fswift-irc-generated/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nathanborror%2Fswift-irc-generated/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nathanborror","download_url":"https://codeload.github.com/nathanborror/swift-irc-generated/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nathanborror%2Fswift-irc-generated/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279019137,"owners_count":26086685,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-14T02:00:06.444Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["generated-code","sonnet-4-5","zed"],"created_at":"2025-10-14T14:54:33.054Z","updated_at":"2025-10-14T14:54:37.002Z","avatar_url":"https://github.com/nathanborror.png","language":"Swift","readme":"# IRC\n\nA modern, Swift-native IRC client library built with Swift 6 concurrency features.\n\n## Features\n\n- **Modern Swift Concurrency**: Built from the ground up with async/await, actors, and structured concurrency\n- **Type-Safe**: Strongly typed messages, commands, and responses\n- **AsyncStream Events**: Real-time event streaming using Swift's AsyncStream\n- **IRCv3 Support**: CAP negotiation, SASL authentication, message tags, and more\n- **Aggregated Queries**: Clean API for multi-message responses (WHOIS, NAMES, WHO, LIST, MOTD)\n- **Rate Limiting**: Built-in configurable rate limiting to prevent flooding\n- **TLS Support**: Secure connections via Network.framework\n- **Actor-Isolated**: Thread-safe by design using Swift actors\n- **Comprehensive Parsing**: Full IRC message parsing including tags, prefix, and parameters\n\n## Requirements\n\n- iOS 18.0+ / macOS 15.0+\n- Swift 6.2+\n- Xcode 16.0+\n\n## Installation\n\n### Swift Package Manager\n\nAdd the following to your `Package.swift`:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/nathanborror/swift-irc-generated\", branch: \"main\")\n]\n```\n\nOr add it via Xcode: File → Add Package Dependencies\n\n## Quick Start\n\n```swift\nimport IRC\n\n// Configure the client\nlet config = Client.Config(\n    server: \"irc.libera.chat\",\n    port: 6697,\n    useTLS: true,\n    nick: \"SwiftBot\",\n    username: \"swiftbot\",\n    realname: \"Swift IRC Bot\"\n)\n\n// Create transport and client\nlet transport = NWTransport()\nlet client = Client(config: config, transport: transport)\n\n// Connect\ntry await client.connect()\n\n// Listen for events\nTask {\n    for await event in client.events {\n        switch event {\n        case .registered:\n            print(\"✅ Connected and registered!\")\n            try await client.join(\"#swift\")\n\n        case .privmsg(let target, let sender, let text, _):\n            print(\"[\\(target)] \u003c\\(sender)\u003e \\(text)\")\n\n            if text.hasPrefix(\"!hello\") {\n                try await client.privmsg(target, \"Hello, \\(sender)!\")\n            }\n\n        case .join(let channel, let nick, _):\n            print(\"→ \\(nick) joined \\(channel)\")\n\n        case .part(let channel, let nick, let reason, _):\n            print(\"← \\(nick) left \\(channel)\" + (reason.map { \": \\($0)\" } ?? \"\"))\n\n        case .error(let error):\n            print(\"❌ Error: \\(error)\")\n\n        case .disconnected(let error):\n            print(\"Disconnected: \\(error?.localizedDescription ?? \"cleanly\")\")\n\n        default:\n            break\n        }\n    }\n}\n\n// Wait for registration\nawait client.awaitRegistered()\n\n// Send messages\ntry await client.privmsg(\"#swift\", \"Hello from Swift!\")\n```\n\n## Configuration\n\nThe `Client.Config` provides extensive configuration options:\n\n```swift\nlet config = Client.Config(\n    server: \"irc.libera.chat\",\n    port: 6697,\n    useTLS: true,\n    nick: \"MyBot\",\n    username: \"mybot\",              // Defaults to nick\n    realname: \"My IRC Bot\",         // Defaults to nick\n    password: nil,                   // Server password (not NickServ)\n    sasl: .plain(                    // SASL authentication\n        username: \"mybot\",\n        password: \"secret\"\n    ),\n    requestedCaps: [                 // IRCv3 capabilities\n        \"sasl\",\n        \"echo-message\",\n        \"message-tags\",\n        \"server-time\",\n        \"account-tag\",\n        \"extended-join\",\n        \"multi-prefix\"\n    ],\n    autoReconnect: false,            // Auto-reconnect on disconnect\n    reconnectDelay: 5.0,             // Seconds between reconnect attempts\n    pingTimeout: 120.0,              // Seconds before ping timeout\n    rateLimit: .default              // Rate limiting strategy\n)\n```\n\n### Rate Limiting\n\nConfigure rate limiting to prevent flooding:\n\n```swift\n// Default: 5 messages per 2 seconds\nconfig.rateLimit = .default\n\n// Custom rate limit\nconfig.rateLimit = Client.Config.RateLimit(\n    messagesPerWindow: 10,\n    windowDuration: 5.0\n)\n\n// No rate limiting (not recommended)\nconfig.rateLimit = .none\n```\n\n## SASL Authentication\n\nAuthenticate with services using SASL:\n\n```swift\n// SASL PLAIN\nconfig.sasl = .plain(\n    username: \"mybot\",\n    password: \"mypassword\"\n)\n\n// SASL EXTERNAL (for CertFP)\nconfig.sasl = .external\n```\n\n## Commands\n\n### Basic Commands\n\n```swift\n// Join/Part channels\ntry await client.join(\"#channel\")\ntry await client.join(\"#secret\", key: \"password\")\ntry await client.part(\"#channel\")\ntry await client.part(\"#channel\", reason: \"Goodbye!\")\n\n// Send messages\ntry await client.privmsg(\"#channel\", \"Hello!\")\ntry await client.notice(\"#channel\", \"Notice message\")\ntry await client.privmsg(\"Username\", \"Private message\")\n\n// Change nick\ntry await client.setNick(\"NewNick\")\n\n// Topic management\ntry await client.setTopic(\"#channel\", topic: \"New topic\")\ntry await client.getTopic(\"#channel\")\n\n// Channel moderation\ntry await client.kick(\"#channel\", nick: \"BadUser\", reason: \"Spam\")\ntry await client.invite(\"Friend\", to: \"#private\")\ntry await client.setMode(\"#channel\", modes: \"+m\")\ntry await client.setMode(\"MyNick\", modes: \"+i\")\n\n// Away status\ntry await client.away(\"Be right back\")\ntry await client.away() // Clear away status\n```\n\n### Aggregated Queries\n\nSome IRC commands return multiple messages. The library aggregates these automatically:\n\n```swift\n// WHOIS - Get detailed user information\nlet whois = try await client.whois(\"SomeUser\")\nprint(\"Nick: \\(whois.nick)\")\nprint(\"Username: \\(whois.username ?? \"unknown\")\")\nprint(\"Host: \\(whois.host ?? \"unknown\")\")\nprint(\"Real name: \\(whois.realname ?? \"unknown\")\")\nprint(\"Channels: \\(whois.channels.joined(separator: \", \"))\")\nprint(\"Idle: \\(whois.idleSeconds ?? 0) seconds\")\n\n// NAMES - Get all users in a channel\nlet names = try await client.names(\"#swift\")\nprint(\"Users in \\(names.channel): \\(names.names.count)\")\nfor name in names.names {\n    print(\"  \\(name)\")\n}\n\n// WHO - Get detailed channel/user information\nlet who = try await client.who(\"#swift\")\nfor entry in who.entries {\n    print(\"\\(entry.nick): \\(entry.username)@\\(entry.host)\")\n}\n\n// LIST - Get list of channels\nlet list = try await client.list()\nfor channel in list.entries {\n    print(\"\\(channel.channel) (\\(channel.userCount)): \\(channel.topic)\")\n}\n\n// MOTD - Get server message of the day\nlet motd = try await client.motd()\nfor line in motd.lines {\n    print(line)\n}\n```\n\n## Event Handling\n\nThe client provides a rich event stream:\n\n```swift\nfor await event in client.events {\n    switch event {\n    case .connected:\n        print(\"Connected to server\")\n\n    case .registered:\n        print(\"Registration complete\")\n\n    case .disconnected(let error):\n        print(\"Disconnected: \\(error?.localizedDescription ?? \"cleanly\")\")\n\n    case .privmsg(let target, let sender, let text, let message):\n        // target: channel or your nick\n        // sender: who sent it\n        // text: message content\n        // message: full Message struct with tags, etc.\n        print(\"[\\(target)] \u003c\\(sender)\u003e \\(text)\")\n\n    case .notice(let target, let sender, let text, _):\n        print(\"[\\(target)] -\\(sender)- \\(text)\")\n\n    case .join(let channel, let nick, _):\n        print(\"\\(nick) joined \\(channel)\")\n\n    case .part(let channel, let nick, let reason, _):\n        print(\"\\(nick) left \\(channel)\")\n\n    case .quit(let nick, let reason, _):\n        print(\"\\(nick) quit: \\(reason ?? \"\")\")\n\n    case .kick(let channel, let kicked, let by, let reason, _):\n        print(\"\\(kicked) was kicked from \\(channel) by \\(by): \\(reason ?? \"\")\")\n\n    case .nick(let oldNick, let newNick, _):\n        print(\"\\(oldNick) is now known as \\(newNick)\")\n\n    case .topic(let channel, let topic, _):\n        print(\"Topic for \\(channel): \\(topic ?? \"no topic\")\")\n\n    case .mode(let target, let modes, _):\n        print(\"Mode \\(modes) on \\(target)\")\n\n    case .error(let error):\n        print(\"Error: \\(error)\")\n\n    case .message(let message):\n        // All raw messages come through here too\n        // Use for handling custom numeric replies or extensions\n        print(\"Raw: \\(message.raw)\")\n    }\n}\n```\n\n## Message Parsing\n\nMessages are automatically parsed with rich metadata:\n\n```swift\nlet message = Message.parse(\":nick!user@host PRIVMSG #channel :Hello world\")\n\nprint(message.prefix)        // \"nick!user@host\"\nprint(message.nick)          // \"nick\"\nprint(message.user)          // \"user\"\nprint(message.host)          // \"host\"\nprint(message.command)       // \"PRIVMSG\"\nprint(message.params)        // [\"#channel\", \"Hello world\"]\nprint(message.target)        // \"#channel\"\nprint(message.text)          // \"Hello world\"\nprint(message.channel)       // \"#channel\"\n\n// IRCv3 tags\nlet taggedMessage = Message.parse(\n    \"@time=2024-01-01T12:00:00.000Z :nick!user@host PRIVMSG #channel :Hi\"\n)\nprint(taggedMessage.tags[\"time\"]) // \"2024-01-01T12:00:00.000Z\"\n\n// Numeric replies\nlet numericMsg = Message.parse(\":server 001 nick :Welcome!\")\nprint(numericMsg.isNumeric)       // true\nprint(numericMsg.numericCode)     // 1\nprint(numericMsg.numericName)     // \"RPL_WELCOME\"\n```\n\n## Architecture\n\n### Core Components\n\n1. **Client (Actor)**: Main interface, handles connection lifecycle and message routing\n2. **Transport (Protocol)**: Abstraction for network I/O\n   - `NWTransport`: Production implementation using Network.framework\n   - `MockTransport`: Testing implementation with in-memory streams\n3. **Message**: Parsed IRC message with tags, prefix, command, and parameters\n4. **Command**: Type-safe outgoing command builder\n5. **Aggregations**: Actors that collect multi-message responses\n\n### Async Architecture\n\nThe library leverages Swift's modern concurrency features:\n\n- **Actor isolation** ensures thread-safe state management\n- **AsyncStream** provides backpressure-aware event streaming\n- **Structured concurrency** with task groups for managing I/O loops\n- **Continuations** bridge callback-based network APIs with async/await\n\n### Message Flow\n\n```\n┌─────────────┐\n│   Network   │\n└──────┬──────┘\n       │ readLine()\n       ▼\n┌─────────────┐\n│ Read Loop   │\n└──────┬──────┘\n       │ parse()\n       ▼\n┌─────────────┐\n│   Message   │\n└──────┬──────┘\n       │\n       ├─────────► Aggregations (WHOIS, NAMES, etc.)\n       │\n       ├─────────► Protocol Handlers (CAP, SASL, PING)\n       │\n       └─────────► Event Stream (user consumption)\n```\n\n### Connection Lifecycle\n\n1. **Disconnected** → `connect()`\n2. **Connecting** → Transport opens socket\n3. **Connected** → Start I/O loops\n4. **Registering** → CAP LS, NICK, USER, SASL (if configured)\n5. **Registered** → Ready for commands, emit `.registered` event\n6. **Disconnected** → Cleanup, emit `.disconnected` event\n\n## Testing\n\nUse `MockTransport` for testing without real connections:\n\n```swift\nimport IRC\n\nlet transport = MockTransport()\nlet client = Client(config: config, transport: transport)\n\n// Queue server responses\nawait transport.queueRead(\":server 001 nick :Welcome!\")\nawait transport.queueRead(\":server 376 nick :End of MOTD\")\n\ntry await client.connect()\nawait client.awaitRegistered()\n\n// Verify sent commands\nlet written = await transport.getWrittenLines()\nXCTAssertTrue(written.contains(\"NICK SwiftBot\"))\n```\n\n## Examples\n\n### Simple Echo Bot\n\n```swift\nlet transport = NWTransport()\nlet config = Client.Config(\n    server: \"irc.libera.chat\",\n    port: 6697,\n    useTLS: true,\n    nick: \"EchoBot\"\n)\nlet client = Client(config: config, transport: transport)\n\ntry await client.connect()\nawait client.awaitRegistered()\ntry await client.join(\"#bots\")\n\nfor await event in client.events {\n    if case .privmsg(let target, let sender, let text, _) = event {\n        if text.hasPrefix(\"!echo \") {\n            let reply = String(text.dropFirst(6))\n            try await client.privmsg(target, \"\\(sender): \\(reply)\")\n        }\n    }\n}\n```\n\n### URL Title Bot\n\n```swift\nimport Foundation\n\nfor await event in client.events {\n    if case .privmsg(let target, _, let text, _) = event {\n        if let url = extractURL(from: text) {\n            if let title = try? await fetchTitle(from: url) {\n                try await client.privmsg(target, \"📎 \\(title)\")\n            }\n        }\n    }\n}\n\nfunc extractURL(from text: String) -\u003e URL? {\n    // URL extraction logic\n}\n\nfunc fetchTitle(from url: URL) async throws -\u003e String {\n    // Fetch and parse HTML title\n}\n```\n\n### Channel Logger\n\n```swift\nimport Foundation\n\nlet logger = FileHandle(forWritingAtPath: \"irc.log\")!\n\nfor await event in client.events {\n    let timestamp = ISO8601DateFormatter().string(from: Date())\n\n    switch event {\n    case .privmsg(let target, let sender, let text, _):\n        let line = \"[\\(timestamp)] [\\(target)] \u003c\\(sender)\u003e \\(text)\\n\"\n        logger.write(line.data(using: .utf8)!)\n\n    case .join(let channel, let nick, _):\n        let line = \"[\\(timestamp)] [\\(channel)] → \\(nick) joined\\n\"\n        logger.write(line.data(using: .utf8)!)\n\n    // ... other events\n\n    default:\n        break\n    }\n}\n```\n\n## Low-Level Access\n\nFor advanced use cases, you can send raw IRC commands:\n\n```swift\n// Send raw command\ntry await client.sendRaw(\"PRIVMSG #channel :Hello\")\n\n// Use Command enum (encodes automatically)\ntry await client.send(.raw(\"MODE #channel +m\"))\n\n// Access raw messages\nfor await event in client.events {\n    if case .message(let message) = event {\n        // Handle any message type\n        print(\"Command: \\(message.command)\")\n        print(\"Params: \\(message.params)\")\n        print(\"Raw: \\(message.raw)\")\n\n        // Check numeric codes\n        if let code = message.numericCode {\n            switch code {\n            case 353: // RPL_NAMREPLY\n                print(\"Names: \\(message.text ?? \"\")\")\n            default:\n                break\n            }\n        }\n    }\n}\n```\n\n## Error Handling\n\n```swift\ndo {\n    try await client.connect()\n    await client.awaitRegistered()\n    try await client.join(\"#channel\")\n} catch let error as TransportError {\n    print(\"Transport error: \\(error)\")\n} catch let error as ClientError {\n    print(\"Client error: \\(error)\")\n} catch {\n    print(\"Unknown error: \\(error)\")\n}\n```\n\n## Best Practices\n\n1. **Always await registration** before sending commands (except CAP, NICK, USER, PASS)\n2. **Handle disconnections** gracefully and implement reconnection logic if needed\n3. **Use rate limiting** to avoid being kicked for flooding\n4. **Process events asynchronously** using separate tasks for long-running operations\n5. **Clean up** by calling `disconnect()` when done\n\n## Performance Considerations\n\n- The client uses a single actor for thread safety, which serializes all operations\n- Event processing is async, so slow handlers won't block the read loop\n- Rate limiting prevents server-side throttling but adds latency to high-volume bots\n- Message parsing is lazy where possible (tags, prefix parsing)\n\n## Contributing\n\nContributions are welcome! Please:\n\n1. Fork the repository\n2. Create a feature branch\n3. Add tests for new functionality\n4. Ensure all tests pass\n5. Submit a pull request\n\n## License\n\nMIT License - see LICENSE file for details\n\n## Resources\n\n- [IRC RFC 1459](https://tools.ietf.org/html/rfc1459)\n- [IRC RFC 2812](https://tools.ietf.org/html/rfc2812)\n- [IRCv3 Specifications](https://ircv3.net/irc/)\n- [Swift Concurrency](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html)\n\n## Credits\n\nBuilt with ❤️ using Swift 6 and modern concurrency features.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnathanborror%2Fswift-irc-generated","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnathanborror%2Fswift-irc-generated","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnathanborror%2Fswift-irc-generated/lists"}