Source Code

(define-map messages
  { message-id: uint }
  {
    author: principal,
    content: (string-utf8 500),
    created-at: uint,
    is-public: bool,
    recipient: (optional principal)
  }
)

(define-map message-reactions
  { message-id: uint, user: principal }
  { reaction-type: (string-ascii 20), reacted-at: uint }
)

(define-data-var message-counter uint u0)

(define-constant ERR-NOT-FOUND (err u404))
(define-constant ERR-UNAUTHORIZED (err u403))
(define-constant ERR-INVALID-INPUT (err u400))
(define-constant MAX-CONTENT-LENGTH u500)

(define-private (is-valid-content (content (string-utf8 500)))
  (and (> (len content) u0) (<= (len content) MAX-CONTENT-LENGTH))
)

(define-public (post-public-message (content (string-utf8 500)))
  (let
    (
      (message-id (var-get message-counter))
    )
    (asserts! (is-valid-content content) ERR-INVALID-INPUT)
    
    (map-set messages
      { message-id: message-id }
      {
        author: tx-sender,
        content: content,
        created-at: burn-block-height,
        is-public: true,
        recipient: none
      }
    )
    
    (var-set message-counter (+ message-id u1))
    (ok message-id)
  )
)

(define-public (send-direct-message (recipient principal) (content (string-utf8 500)))
  (let
    (
      (message-id (var-get message-counter))
    )
    (asserts! (is-valid-content content) ERR-INVALID-INPUT)
    (asserts! (not (is-eq tx-sender recipient)) ERR-INVALID-INPUT)
    
    (map-set messages
      { message-id: message-id }
      {
        author: tx-sender,
        content: content,
        created-at: burn-block-height,
        is-public: false,
        recipient: (some recipient)
      }
    )
    
    (var-set message-counter (+ message-id u1))
    (ok message-id)
  )
)

(define-public (react-to-message (message-id uint) (reaction-type (string-ascii 20)))
  (let
    (
      (message (unwrap! (map-get? messages { message-id: message-id }) ERR-NOT-FOUND))
      (existing-reaction (map-get? message-reactions { message-id: message-id, user: tx-sender }))
    )
    (asserts! (> (len reaction-type) u0) ERR-INVALID-INPUT)
    (asserts! (<= (len reaction-type) u20) ERR-INVALID-INPUT)
    
    (map-set message-reactions
      { message-id: message-id, user: tx-sender }
      { reaction-type: reaction-type, reacted-at: burn-block-height }
    )
    
    (ok true)
  )
)

(define-public (remove-reaction (message-id uint))
  (let
    (
      (existing-reaction (unwrap! (map-get? message-reactions { message-id: message-id, user: tx-sender }) ERR-NOT-FOUND))
    )
    (map-delete message-reactions { message-id: message-id, user: tx-sender })
    (ok true)
  )
)

(define-read-only (get-message (message-id uint))
  (map-get? messages { message-id: message-id })
)

(define-read-only (get-message-count)
  (ok (var-get message-counter))
)

(define-read-only (get-reaction (message-id uint) (user principal))
  (map-get? message-reactions { message-id: message-id, user: user })
)

(define-read-only (can-read-message (message-id uint) (reader principal))
  (match (map-get? messages { message-id: message-id })
    message
      (if (get is-public message)
        (ok true)
        (ok (or 
          (is-eq reader (get author message))
          (is-eq (some reader) (get recipient message))
        ))
      )
    (ok false)
  )
)

Functions (9)

FunctionAccessArgs
is-valid-contentprivatecontent: (string-utf8 500
post-public-messagepubliccontent: (string-utf8 500
send-direct-messagepublicrecipient: principal, content: (string-utf8 500
react-to-messagepublicmessage-id: uint, reaction-type: (string-ascii 20
remove-reactionpublicmessage-id: uint
get-messageread-onlymessage-id: uint
get-message-countread-only
get-reactionread-onlymessage-id: uint, user: principal
can-read-messageread-onlymessage-id: uint, reader: principal