webexjrcd

An IRC-to-Webex Teams bridge written in J

Download webexjrcd.ijs

webexjrcd is an IRC server that bridges your favorite IRC client to Cisco Webex Teams. Connect using any standard IRC client and interact with your Webex rooms, direct messages, and team conversations through the familiar IRC interface.


Features

Message Handling

Room Management

User Information


Quick Start

1. Get Your Credentials

Option A: Personal Access Token (quick testing, expires in ~12 hours) - Visit developer.webex.com - Copy your personal access token

Option B: OAuth Integration (recommended, lasts ~90 days) - Create an integration at developer.webex.com - Set redirect URI to http://localhost:8667/callback - Request spark:all scope - Note your Client ID and Client Secret

2. Start the Server

jconsole webexjrcd.ijs

3. Connect Your IRC Client

Server:   localhost
Port:     6661
Nickname: your_webex_username  (part before @ in your email)
Password: <see Authentication section>

Authentication

webexjrcd supports three authentication formats via the IRC PASS command:

Format 1: Bearer Token

PASS <bearerToken>

Personal access tokens from the Webex Developer Portal. Expires in ~12 hours.

Format 2: OAuth Flow

PASS <clientId>:<clientSecret>

Triggers browser-based OAuth:

┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
│   IRC Client    │      │   webexjrcd     │      │  Webex OAuth    │
└────────┬────────┘      └────────┬────────┘      └────────┬────────┘
         │                        │                        │
         │ PASS clientId:secret   │                        │
         │───────────────────────>│                        │
         │                        │                        │
         │ NOTICE: Open this URL  │                        │
         │<───────────────────────│                        │
         │                        │                        │
         │        ┌───────────────┴───────────────┐        │
         │        │ User opens URL in browser     │        │
         │        │ and authorizes the app        │        │
         │        └───────────────┬───────────────┘        │
         │                        │                        │
         │                        │ Authorization code     │
         │                        │<───────────────────────│
         │                        │                        │
         │                        │ Exchange for tokens    │
         │                        │───────────────────────>│
         │                        │                        │
         │                        │ Access + Refresh token │
         │                        │<───────────────────────│
         │                        │                        │
         │ NOTICE: Save this PASS │                        │
         │<───────────────────────│                        │
         │                        │                        │
         │ Welcome messages       │                        │
         │<───────────────────────│                        │

You have 3 minutes to complete browser authorization.

Format 3: Refresh Token

PASS <clientId>:<clientSecret>:<refreshToken>

Use a saved refresh token from a previous OAuth flow. Lasts ~90 days. When it’s refreshed, you’ll receive the new token to save.


Commands Reference

Bridge-Specific Commands

Command Alias Description
;help Show help message
;log [N] ;l [N] Fetch last N messages (default: 100)
;reply <suffix> <msg> ;r <suffix> <msg> Reply to message, creating/continuing thread
;delete <suffix> ;d <suffix> Delete a message
s/old/new/ Edit your last message
/me <action> Send action message

Standard IRC Commands

Command Description
/list List all Webex rooms
/join <channel\|roomId> Join a room
/part <channel> Leave channel locally
/topic [channel] View room title and ID
/msg <nick> <message> Send direct message
/names [channel] List channel members
/whois <nick> User info with status/title
/who <channel> List members with H/G flags
/mode <target> Query modes
/userhost <nick> Get user@host
/ison <nick>... Check if users online
/time Server time
/version Server version
/lusers User/channel stats
/stats Detailed server stats
/quit Disconnect

Admin Commands

Command Description
EXIT Shutdown server
RESTART Reload and restart

Message Format & Threading

Standard Messages

sender [abc] message content

The [abc] suffix is the last 3 characters of the Webex message ID. This suffix is used to reference messages for replies, edits, and deletions.

Threaded Messages

When a message is part of a thread (i.e., it has a parentId in Webex), it displays with the parent’s suffix:

alice [x2F] Let's discuss the new feature
bob   <x2F| [g7H] I have some ideas
carol <x2F| [k9J] Me too!

The <x2F| prefix indicates this message is a reply in the thread started by message x2F.

Thread Visualization

alice [x2F] Let's discuss the new feature    ← Thread parent (no prefix)
    │
    ├── bob   <x2F| [g7H] I have some ideas  ← Reply to x2F
    │
    └── carol <x2F| [k9J] Me too!            ← Reply to x2F

How Replies Work

When you use ;reply or ;r, the message you reference becomes the thread parent:

Scenario 1: Starting a new thread
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
alice [x2F] Anyone seen the new design?     ← Standalone message

You type: ;r x2F Looks great!

Result in Webex:
┌─ Thread ─────────────────────────────┐
│ alice: Anyone seen the new design?   │  ← parentId = null (thread root)
│   └─ you: Looks great!               │  ← parentId = x2F's full ID
└──────────────────────────────────────┘

Displayed in IRC:
alice [x2F] Anyone seen the new design?
you   <x2F| [m3P] Looks great!


Scenario 2: Continuing an existing thread
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
alice [x2F] Anyone seen the new design?
bob   <x2F| [g7H] I have some ideas

You type: ;r x2F Me too!

Result: Your message joins the same thread as bob's
you   <x2F| [k9J] Me too!


Scenario 3: Replying to any message in a thread
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
You can reply to ANY message suffix, and your reply
uses that message's ID as the parentId:

alice [x2F] Anyone seen the new design?
bob   <x2F| [g7H] I have some ideas

You type: ;r g7H @bob tell me more

Result in Webex: Creates reply with parentId = g7H's full ID
(This effectively replies to bob's message)

The Suffix Mapping System

webexjrcd maintains a mapping of suffixes to full message IDs:

┌─────────────────────────────────────────────────────────────┐
│                    messageSuffixes table                     │
├─────────┬──────────────────────────────────────┬────────────┤
│ suffix  │ messageId                            │ roomId     │
├─────────┼──────────────────────────────────────┼────────────┤
│ x2F     │ Y2lzY29zcGFyazovL3VzL01FU1NBR0Uv... │ Y2lzY29...│
│ g7H     │ Y2lzY29zcGFyazovL3VzL01FU1NBR0Uv... │ Y2lzY29...│
│ k9J     │ Y2lzY29zcGFyazovL3VzL01FU1NBR0Uv... │ Y2lzY29...│
└─────────┴──────────────────────────────────────┴────────────┘

When you type: ;r x2F my message

1. Look up 'x2F' in messageSuffixes → get full messageId
2. POST to Webex API:
   {
     "roomId": "Y2lzY29...",
     "parentId": "Y2lzY29zcGFyazovL3VzL01FU1NBR0Uv...",
     "markdown": "my message"
   }
3. Webex creates the message in the thread

Architecture & Implementation

System Overview

┌─────────────────────────────────────────────────────────────────────┐
│                         webexjrcd                                    │
│                                                                      │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────────────┐  │
│  │  IRC Server  │    │   Protocol   │    │    Webex API         │  │
│  │              │◄──►│   Bridge     │◄──►│    Client            │  │
│  │  Port 6661   │    │              │    │                      │  │
│  └──────────────┘    └──────────────┘    └──────────────────────┘  │
│         │                   │                      │                │
│         ▼                   ▼                      ▼                │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────────────┐  │
│  │   Socket     │    │    State     │    │   REST Calls via     │  │
│  │   Handling   │    │   Manager    │    │   curl/gethttp       │  │
│  │  (jsocket)   │    │              │    │                      │  │
│  └──────────────┘    └──────────────┘    └──────────────────────┘  │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘
           │                                          │
           ▼                                          ▼
    ┌─────────────┐                          ┌─────────────────┐
    │ IRC Clients │                          │  Webex Cloud    │
    │ (any)       │                          │  webexapis.com  │
    └─────────────┘                          └─────────────────┘

Data Flow

                    OUTBOUND (IRC → Webex)

┌──────────┐                                           ┌──────────┐
│  User    │  PRIVMSG #channel :hello                  │  Webex   │
│  types   │ ─────────────────────────────────────────►│  Room    │
│  message │                                           │          │
└──────────┘                                           └──────────┘
                 │
                 ▼
           ┌───────────┐
           │ ircRoute  │
           │ dispatches│
           │ to handler│
           └─────┬─────┘
                 │
                 ▼
           ┌───────────────┐
           │ircHandlePRIVMSG│
           └───────┬───────┘
                   │
         ┌─────────┼─────────┐
         ▼         ▼         ▼
    ┌─────────┐ ┌─────┐ ┌─────────┐
    │;reply?  │ │s/?  │ │ regular │
    │;delete? │ │edit │ │ message │
    │;log?    │ │     │ │         │
    └────┬────┘ └──┬──┘ └────┬────┘
         │         │         │
         ▼         ▼         ▼
    ┌─────────┐ ┌──────────┐ ┌──────────┐
    │webex    │ │webex     │ │webexSend │
    │Reply    │ │Update    │ │          │
    └─────────┘ └──────────┘ └──────────┘
         │         │              │
         └─────────┼──────────────┘
                   ▼
            POST /v1/messages
            {roomId, markdown, [parentId]}



                    INBOUND (Webex → IRC)

┌──────────┐       every 5 seconds                     ┌──────────┐
│  Webex   │◄──────────────────────────────────────────│webexjrcd │
│  Rooms   │  GET /rooms?max=10&sortBy=lastactivity    │          │
└──────────┘                                           └──────────┘
                          │
                          ▼
                    ┌───────────┐
                    │updateRooms│
                    │ (delta)   │
                    └─────┬─────┘
                          │ rooms with new activity
                          ▼
                    ┌───────────────┐
                    │ For each room:│
                    │ GET /messages │
                    │ ?roomId=X     │
                    └───────┬───────┘
                            │
                            ▼
                    ┌───────────────┐
                    │ Filter by     │
                    │ timestamp >   │
                    │ touchTime     │
                    └───────┬───────┘
                            │ new messages only
                            ▼
                    ┌───────────────┐
                    │ ircPrivmsg    │
                    │ with [suffix] │
                    │ and <parent|  │
                    └───────────────┘
                            │
                            ▼
              @time=2024-01-15T10:30:00Z
              :alice!user@webex PRIVMSG #channel :[x2F] hello

Core Data Structures

NB. Room cache: (lastActivity ; roomId ; cleanedName ; originalTitle)
rooms =: 0 4 $ a:

NB. Example:
┌────────────────────┬──────────────────────┬─────────────────┬──────────────────┐
2024-01-15T10:30:00Z│Y2lzY29zcGFyazovL3Vz..#Engineering_Team│Engineering Team  │
├────────────────────┼──────────────────────┼─────────────────┼──────────────────┤
2024-01-15T09:15:00Z│Y2lzY29zcGFyazovL3Vz..#General_Chat    │General Chat      │
└────────────────────┴──────────────────────┴─────────────────┴──────────────────┘


NB. Connected clients: (socket ; nick ; channels)
clients =: 0 3 $ a:


NB. Message suffix mapping for replies/deletes: (suffix ; messageId ; roomId)
messageSuffixes =: 0 3 $ a:

┌──────┬──────────────────────────────────────┬────────────────────────┐
│ x2F  │ Y2lzY29zcGFyazovL3VzL01FU1NBR0Uv... │ Y2lzY29zcGFyazovL3Vz..
├──────┼──────────────────────────────────────┼────────────────────────┤
│ g7H  │ Y2lzY29zcGFyazovL3VzL01FU1NBR0Uv... │ Y2lzY29zcGFyazovL3Vz..
└──────┴──────────────────────────────────────┴────────────────────────┘


NB. Last message per user per channel for editing: list of boxes
NB. Each box contains: (channel ; nick ; messageId ; text)
lastMessages =: 0$a:

Polling & Delta Updates

The bridge uses an efficient delta-polling strategy to minimize API calls:

┌─────────────────────────────────────────────────────────────────┐
│                    INITIAL CONNECTION                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  GET /rooms?max=1000&sortBy=lastactivity                        │
│                                                                  │
│  • Fetch metadata for ALL rooms once                            │
│  • Store: lastActivity, roomId, title                           │
│  • Set touchTime = most recent lastActivity                     │
│                                                                  │
│  Result: Full room list cached locally                          │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    EVERY 5 SECONDS                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. GET /rooms?max=10&sortBy=lastactivity                       │
│     └─► Only fetch 10 most recently active rooms                │
│                                                                  │
│  2. Compare each room's lastActivity > touchTime                │
│     └─► Identify rooms with new messages                        │
│                                                                  │
│  3. For changed rooms: GET /messages?roomId=X                   │
│     └─► Fetch messages from those rooms                         │
│                                                                  │
│  4. Filter messages by created > touchTime                      │
│     └─► Only process truly new messages                         │
│                                                                  │
│  5. Update touchTime to newest message timestamp                │
│                                                                  │
│  Result: ~99% reduction in API calls vs polling all rooms       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

IRC Protocol Implementation

┌────────────────────────────────────────────────────────────────┐
│                    REGISTRATION SEQUENCE                        │
├────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Client                              Server                     │
│    │                                    │                       │
│    │ CAP LS 302                         │                       │
│    │───────────────────────────────────►│                       │
│    │                                    │                       │
│    │◄───────────────────────────────────│                       │
│    │ CAP * LS :server-time              │                       │
│    │                                    │                       │
│    │ CAP REQ :server-time               │                       │
│    │───────────────────────────────────►│                       │
│    │                                    │                       │
│    │◄───────────────────────────────────│                       │
│    │ CAP * ACK :server-time             │                       │
│    │                                    │                       │
│    │ PASS token                         │                       │
│    │───────────────────────────────────►│                       │
│    │                                    │                       │
│    │ NICK myusername                    │                       │
│    │───────────────────────────────────►│                       │
│    │                                    │                       │
│    │ USER myusername 0 * :realname      │                       │
│    │───────────────────────────────────►│                       │
│    │                                    │                       │
│    │ CAP END                            │                       │
│    │───────────────────────────────────►│                       │
│    │                                    │                       │
│    │    [Server validates PASS token]   │                       │
│    │    [Fetches rooms from Webex]      │                       │
│    │                                    │                       │
│    │◄───────────────────────────────────│                       │
│    │ 001-005 Welcome messages           │                       │
│    │ 375-376 MOTD (Webex ASCII logo)    │                       │
│    │                                    │                       │
│                                                                 │
└────────────────────────────────────────────────────────────────┘

Supported IRC numerics:

Registration:
  001 RPL_WELCOME           Welcome message
  002 RPL_YOURHOST          Host info
  003 RPL_CREATED           Server creation
  004 RPL_MYINFO            Server info
  005 RPL_ISUPPORT          Supported features (CHANTYPES=#)
  251 RPL_LUSERCLIENT       User count

MOTD:
  375 RPL_MOTDSTART         MOTD start
  372 RPL_MOTD              MOTD content (Webex ASCII logo)
  376 RPL_ENDOFMOTD         MOTD end

Channel:
  321 RPL_LISTSTART         LIST header
  322 RPL_LIST              Channel entry
  323 RPL_LISTEND           LIST end
  324 RPL_CHANNELMODEIS     Channel modes (+nt)
  332 RPL_TOPIC             Channel topic
  353 RPL_NAMREPLY          NAMES list (chunked for large rooms)
  366 RPL_ENDOFNAMES        NAMES end
  368 RPL_ENDOFBANLIST      Ban list end
  347 RPL_ENDOFINVITELIST   Invite list end
  349 RPL_ENDOFEXCEPTLIST   Exception list end

User:
  302 RPL_USERHOST          USERHOST reply
  303 RPL_ISON              ISON reply
  311 RPL_WHOISUSER         WHOIS user info
  312 RPL_WHOISSERVER       WHOIS server
  318 RPL_ENDOFWHOIS        WHOIS end
  315 RPL_ENDOFWHO          WHO end
  352 RPL_WHOREPLY          WHO entry (with H/G flags)

Server:
  219 RPL_ENDOFSTATS        Stats end
  249 RPL_STATSDEBUG        Stats debug line
  254 RPL_LUSERCHANNELS     Channel count
  351 RPL_VERSION           Server version
  391 RPL_TIME              Server time

Errors:
  451 ERR_NOTREGISTERED     Not registered
  461 ERR_NEEDMOREPARAMS    Need more parameters
  464 ERR_PASSWDMISMATCH    Password incorrect

IRCv3:
  CAP LS/REQ/ACK/END        Capability negotiation
  server-time               Message timestamps (@time=...)

JSON Parser (ejson locale)

webexjrcd includes a complete JSON encoder/decoder written in pure J:

NB. Decode JSON string to J data structure
dec_ejson_ '{"name":"alice","count":42,"active":true}'
┌──────┬───────┐
│name  │alice  │
├──────┼───────┤
│count │42
├──────┼───────┤
│active│json_tru│
└──────┴───────┘

NB. Encode J data structure to JSON
enc_ejson_ 2 2 $ 'roomId';'abc123';'text';'hello'
{"roomId":"abc123","text":"hello"}

NB. Select field from decoded JSON
'name' select_ejson_ dec_ejson_ '{"name":"alice"}'
┌─────┐
│alice│
└─────┘

NB. Select multiple fields as columns
('name';'count') selector_ejson_ dec_ejson_ '[{"name":"a","count":1},{"name":"b","count":2}]'
┌─┬─┐
│a│1
├─┼─┤
│b│2
└─┴─┘

The JSON library handles: - Objects, arrays, strings, numbers, booleans, null - Nested structures - Escape sequences (\n, \t, \", \\, etc.) - Unicode escape sequences (stripped for IRC compatibility)

OAuth Implementation

Full OAuth 2.0 authorization code flow implemented in J:

startOAuth =: 3 : 0
  'clientId clientSecret sock nick' =. y

  NB. Generate 32-char random state for CSRF protection
  state =. randomString 32

  NB. Build authorization URL
  authUrl =. 'https://webexapis.com/v1/authorize'
  authUrl =. authUrl,'?client_id=',clientId
  authUrl =. authUrl,'&response_type=code'
  authUrl =. authUrl,'&redirect_uri=http://localhost:',":oauthCallbackPort,'/callback'
  authUrl =. authUrl,'&scope=spark:all'
  authUrl =. authUrl,'&state=',state

  authUrl ; state ; oauthCallbackPort ; clientId ; clientSecret ; nick
)

startOAuthServer =: 4 : 0
  NB. x = port, y = state;clientId;clientSecret;sock;nick

  NB. Create TCP listening socket
  'rc lsock' =. sdsocket 2 1 0
  sdsetsockopt lsock ; SOL_SOCKET ; SO_REUSEADDR ; 1
  sdbind lsock ; 2 ; '0.0.0.0' ; port
  sdlisten lsock , 5

  NB. Wait up to 3 minutes for browser callback
  NB. Uses sdselect with 2-second timeout for non-blocking check

  NB. When connection arrives:
  NB. 1. Parse HTTP GET /callback?code=XXX&state=YYY
  NB. 2. Verify state matches (CSRF protection)
  NB. 3. Exchange code for tokens via POST to /access_token
  NB. 4. Send styled HTML response to browser
  NB. 5. Return refresh token
)

exchangeCodeForTokens =: 4 : 0
  NB. x = authorization code
  NB. y = clientId;clientSecret;port

  NB. POST to https://webexapis.com/v1/access_token
  NB. Content-Type: application/x-www-form-urlencoded
  NB. Body: grant_type=authorization_code&code=X&client_id=Y&...

  NB. Returns: access_token and refresh_token
)

Socket Handling

Non-blocking I/O using J’s jsocket addon:

NB. Main client loop uses sdselect for non-blocking reads
while. connected do.
  NB. Check for data with 100ms timeout
  selectResult =. sdselect csock ; '' ; '' ; 0.1

  if. no data available do.
    6!:3 ] 1  NB. Sleep 1 second to prevent CPU spinning
  else.
    'rc msg' =. sdrecv csock , 4096 , 0
    NB. Process received data...
  end.

  NB. Poll Webex every 5 seconds
  if. (currentTime - lastPollTime) > 5 do.
    newMsgs =. allNewMessages''
    NB. Forward to client...
  end.
end.

Channel Name Sanitization

Webex room titles are cleaned for IRC compatibility:

ircCleanChan =: 3 : 0
  name =. stripUnicodeEscapes y     NB. Remove \uXXXX sequences
  name =. name rplc ' ';'_'         NB. Spaces → underscores
  name =. name rplc ':';';'         NB. Colons → semicolons (: is special in IRC)
  name =. name -. ',',CR,LF,(0 7{a.) NB. Remove invalid chars
  if. '#' ~: {. name do.
    name =. '#',name                NB. Ensure # prefix
  end.
  if. 64 < # name do.
    NB. Truncate long names, append room ID suffix for uniqueness
    suffix =. _3 {. roomId
    name =. (60 {. name), ';', suffix
  end.
  name
)

NB. Examples:
ircCleanChan 'Engineering Team''#Engineering_Team'
ircCleanChan 'Q&A: Ask Me Anything''#Q&A;_Ask_Me_Anything'
ircCleanChan 'Very Long Room Name...''#Very_Long_Room_Name...;x2F'

Configuration

Edit the top of webexjrcd.ijs:

ircPort =: 6661           NB. IRC server port (default: 6661, standard: 6667)
oauthCallbackPort =: 8667 NB. OAuth redirect port (must match Webex integration)
debugEnable =: 1          NB. Debug output (WARNING: shows auth tokens!)
ioEnable =: 1             NB. Show raw IRC protocol lines

Security Note: Set debugEnable =: 0 in production as debug output includes authentication tokens.


Troubleshooting

“Password required” (ERR 464)

You must provide a PASS command before NICK/USER. Configure your IRC client to send the server password on connect.

“Nick mismatch”

Your IRC nickname must exactly match your Webex username (the part before @ in your email). The server verifies this by querying /people/me with your token.

“Token expired”

OAuth timeout

You have 3 minutes to complete browser authorization. If it times out: 1. Disconnect from the IRC server 2. Reconnect with the same PASS to restart OAuth

Missing messages

Port already in use

Change ircPort in the configuration section, or find and stop the process using port 6661:

lsof -i :6661

API Rate Limits

If you see errors about rate limits, the bridge automatically sleeps between polls. With delta polling, you should stay well under Webex limits even with 1000+ rooms.


Technical Specifications

Aspect Details
Language J (jsoftware.com)
Lines of Code ~3600
External Dependencies None (pure J + standard library)
IRC Protocol RFC 1459 + IRCv3 server-time
Webex API REST v1 (webexapis.com)
Max Rooms 1000+ (tested)
Max Members/Room 1000+ (paginated via Link header)
Polling Interval 5 seconds
OAuth Timeout 3 minutes
Token Lifetimes Bearer ~12h, Refresh ~90d
Socket Timeout 100ms (non-blocking)
Idle CPU Usage ~0%

Example Session

* Connecting to localhost:6661...
* Connected. Now logging in.

-!- NOTICE: Fetching all rooms...
-!- NOTICE: Found 247 rooms

-!- Welcome to the webexjrcd Webex IRC Bridge
-!-
-!-                ..';::::::;,'.          .,cldddddddol;'
-!-             .':::;:::::::::::::;.    .:ddddddddddxxxxxxxo,
-!-                      [ASCII Webex Logo]
-!-
-!-                          IRC to Webex Bridge
-!-

/list
#Engineering_Team 1 :Engineering Team :: Y2lzY29zcGFyazov...
#General_Chat 1 :General Chat :: Y2lzY29zcGFyazov...
#Cats_Cats_Cats 1 :Cats Cats Cats :: Y2lzY29zcGFyazov...
...

/join #Engineering_Team
-!- byakuren [~user@webex] has joined #Engineering_Team
-!- Topic for #Engineering_Team: Engineering Team :: Y2lzY29...
-!- Channel #Engineering_Team: 42 nicks

;log 5
<alice> [x2F] Has anyone seen the new API docs?
<bob> <x2F| [g7H] Yes! They look great
<carol> <x2F| [k9J] Agreed, much clearer now
<dave> [m3P] Unrelated: meeting in 10 mins
<eve> [n4Q] Thanks for the reminder

;r x2F I'll review them this afternoon
<byakuren> <x2F| [p5R] I'll review them this afternoon

s/this afternoon/today/
-!- byakuren: I'll review them today

/me grabs coffee
* byakuren grabs coffee

/quit
* Disconnected

License

Written in J. Use freely.


webexjrcd - IRC to Webex Bridge

Download webexjrcd.ijs