We use cookies to enhance your experience on the site
CodeWorlds

Roles, Permissions, and Moderation

Discord has an advanced role system with permissions - essential for moderation and community organization.

Permission System

1// Bitwise permissions like Discord
2enum PermissionBits {
3  CREATE_INSTANT_INVITE = 1 << 0,
4  KICK_MEMBERS = 1 << 1,
5  BAN_MEMBERS = 1 << 2,
6  ADMINISTRATOR = 1 << 3,
7  MANAGE_CHANNELS = 1 << 4,
8  MANAGE_SERVER = 1 << 5,
9  ADD_REACTIONS = 1 << 6,
10  VIEW_AUDIT_LOG = 1 << 7,
11  PRIORITY_SPEAKER = 1 << 8, // voice
12  STREAM = 1 << 9, // voice
13  VIEW_CHANNEL = 1 << 10,
14  SEND_MESSAGES = 1 << 11,
15  SEND_TTS_MESSAGES = 1 << 12,
16  MANAGE_MESSAGES = 1 << 13,
17  EMBED_LINKS = 1 << 14,
18  ATTACH_FILES = 1 << 15,
19  READ_MESSAGE_HISTORY = 1 << 16,
20  MENTION_EVERYONE = 1 << 17,
21  USE_EXTERNAL_EMOJIS = 1 << 18,
22  CONNECT = 1 << 20, // voice
23  SPEAK = 1 << 21, // voice
24  MUTE_MEMBERS = 1 << 22, // voice
25  DEAFEN_MEMBERS = 1 << 23, // voice
26  MOVE_MEMBERS = 1 << 24, // voice
27  USE_VAD = 1 << 25, // voice activity detection
28  CHANGE_NICKNAME = 1 << 26,
29  MANAGE_NICKNAMES = 1 << 27,
30  MANAGE_ROLES = 1 << 28,
31  MANAGE_WEBHOOKS = 1 << 29,
32  MANAGE_EMOJIS = 1 << 30,
33}
34
35// Check if user has permission
36function hasPermission(
37  member: Member,
38  permission: PermissionBits,
39  roles: Role[],
40  channel?: Channel
41): boolean {
42  // Owner has all permissions
43  if (member.isOwner) return true;
44
45  // Get all member roles
46  const memberRoles = roles.filter(r => member.roles.includes(r.id));
47
48  // Check for ADMINISTRATOR
49  if (memberRoles.some(r => r.permissions & PermissionBits.ADMINISTRATOR)) {
50    return true;
51  }
52
53  // Calculate base permissions from roles
54  let permissions = 0;
55  memberRoles.forEach(role => {
56    permissions |= role.permissions;
57  });
58
59  // Apply channel-specific overrides
60  if (channel) {
61    channel.permissions.forEach(override => {
62      if (member.roles.includes(override.roleId)) {
63        // Apply denies first
64        override.deny.forEach(perm => {
65          permissions &= ~perm;
66        });
67        // Then apply allows
68        override.allow.forEach(perm => {
69          permissions |= perm;
70        });
71      }
72    });
73  }
74
75  return (permissions & permission) === permission;
76}

Role Manager

1'use client';
2
3import { useState } from 'react';
4
5interface RoleManagerProps {
6  server: Server;
7  onCreateRole: () => void;
8  onEditRole: (roleId: string) => void;
9  onDeleteRole: (roleId: string) => void;
10}
11
12export default function RoleManager({
13  server,
14  onCreateRole,
15  onEditRole,
16  onDeleteRole
17}: RoleManagerProps) {
18  const [selectedRoleId, setSelectedRoleId] = useState<string | null>(null);
19
20  const selectedRole = server.roles.find(r => r.id === selectedRoleId);
21
22  return (
23    <div className="flex h-screen bg-gray-800">
24      {/* Roles List */}
25      <div className="w-64 bg-gray-900 p-4">
26        <div className="flex items-center justify-between mb-4">
27          <h3 className="font-bold text-white">Roles</h3>
28          <button
29            onClick={onCreateRole}
30            className="w-8 h-8 bg-green-600 hover:bg-green-700 rounded-full text-white"
31          >
32            +
33          </button>
34        </div>
35
36        <div className="space-y-1">
37          {server.roles
38            .sort((a, b) => b.position - a.position)
39            .map(role => (
40              <button
41                key={role.id}
42                onClick={() => setSelectedRoleId(role.id)}
43                className={`w-full text-left px-3 py-2 rounded flex items-center justify-between ${
44                  selectedRoleId === role.id
45                    ? 'bg-gray-700'
46                    : 'hover:bg-gray-800'
47                }`}
48              >
49                <div className="flex items-center gap-2 flex-1 min-w-0">
50                  <div
51                    className="w-3 h-3 rounded-full"
52                    style={{ backgroundColor: role.color || '#99AAB5' }}
53                  />
54                  <span className="text-white text-sm truncate">
55                    {role.name}
56                  </span>
57                </div>
58                <span className="text-xs text-gray-400">
59                  {role.memberCount}
60                </span>
61              </button>
62            ))}
63        </div>
64
65        <div className="mt-4 pt-4 border-t border-gray-800 text-xs text-gray-400">
66          Drag to reorder roles
67        </div>
68      </div>
69
70      {/* Role Editor */}
71      {selectedRole ? (
72        <div className="flex-1 p-6 overflow-y-auto">
73          <div className="max-w-2xl">
74            <h2 className="text-2xl font-bold text-white mb-6">
75              Edit Role: {selectedRole.name}
76            </h2>
77
78            {/* Display */}
79            <div className="bg-gray-700 rounded-lg p-4 mb-6">
80              <h3 className="font-bold text-white mb-3">Display</h3>
81
82              <div className="mb-4">
83                <label className="block text-sm text-gray-300 mb-2">
84                  Role Name
85                </label>
86                <input
87                  type="text"
88                  value={selectedRole.name}
89                  className="w-full bg-gray-900 text-white px-3 py-2 rounded"
90                />
91              </div>
92
93              <div className="mb-4">
94                <label className="block text-sm text-gray-300 mb-2">
95                  Role Color
96                </label>
97                <div className="flex items-center gap-3">
98                  <input
99                    type="color"
100                    value={selectedRole.color || '#99AAB5'}
101                    className="w-16 h-10 rounded cursor-pointer"
102                  />
103                  <span className="text-gray-400 text-sm">
104                    {selectedRole.color || 'Default'}
105                  </span>
106                </div>
107              </div>
108
109              <div className="space-y-2">
110                <label className="flex items-center gap-2 text-sm text-gray-300">
111                  <input
112                    type="checkbox"
113                    checked={selectedRole.isHoisted}
114                    className="w-4 h-4"
115                  />
116                  Display role members separately from online members
117                </label>
118
119                <label className="flex items-center gap-2 text-sm text-gray-300">
120                  <input
121                    type="checkbox"
122                    checked={selectedRole.isMentionable}
123                    className="w-4 h-4"
124                  />
125                  Allow anyone to @mention this role
126                </label>
127              </div>
128            </div>
129
130            {/* Permissions */}
131            <div className="bg-gray-700 rounded-lg p-4">
132              <h3 className="font-bold text-white mb-3">Permissions</h3>
133
134              <div className="space-y-3">
135                {Object.entries(PermissionBits).map(([name, bit]) => {
136                  const hasPermission = (selectedRole.permissions & bit) === bit;
137
138                  return (
139                    <label
140                      key={name}
141                      className="flex items-center gap-3 text-sm text-gray-300 hover:bg-gray-600 p-2 rounded cursor-pointer"
142                    >
143                      <input
144                        type="checkbox"
145                        checked={hasPermission}
146                        className="w-4 h-4"
147                      />
148                      <div className="flex-1">
149                        <div className="font-semibold">
150                          {name.split('_').map(w => w.charAt(0) + w.slice(1).toLowerCase()).join(' ')}
151                        </div>
152                        <div className="text-xs text-gray-400">
153                          {getPermissionDescription(name)}
154                        </div>
155                      </div>
156                    </label>
157                  );
158                })}
159              </div>
160            </div>
161
162            {/* Actions */}
163            <div className="mt-6 flex gap-3">
164              <button
165                onClick={() => onEditRole(selectedRole.id)}
166                className="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded font-semibold"
167              >
168                Save Changes
169              </button>
170              <button
171                onClick={() => onDeleteRole(selectedRole.id)}
172                className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded font-semibold"
173              >
174                Delete Role
175              </button>
176            </div>
177          </div>
178        </div>
179      ) : (
180        <div className="flex-1 flex items-center justify-center text-gray-500">
181          Select a role to edit
182        </div>
183      )}
184    </div>
185  );
186}
187
188function getPermissionDescription(permission: string): string {
189  const descriptions: Record<string, string> = {
190    ADMINISTRATOR: 'Full access to all channels and settings',
191    MANAGE_SERVER: 'Change server name, region, and other settings',
192    MANAGE_ROLES: 'Create, edit, and delete roles lower than this one',
193    MANAGE_CHANNELS: 'Create, edit, and delete channels',
194    KICK_MEMBERS: 'Remove members from the server',
195    BAN_MEMBERS: 'Ban members from the server',
196    SEND_MESSAGES: 'Send messages in text channels',
197    MANAGE_MESSAGES: 'Delete messages sent by other members',
198    MENTION_EVERYONE: 'Use @everyone and @here mentions',
199    CONNECT: 'Connect to voice channels',
200    SPEAK: 'Speak in voice channels',
201    MUTE_MEMBERS: 'Mute members in voice channels',
202    DEAFEN_MEMBERS: 'Deafen members in voice channels',
203    // Add more descriptions...
204  };
205
206  return descriptions[permission] || 'Permission description';
207}

Moderation Actions

1interface ModerationAction {
2  id: string;
3  type: 'kick' | 'ban' | 'timeout' | 'warn' | 'unban';
4  targetUserId: string;
5  moderatorId: string;
6  reason: string;
7  duration?: number; // for timeout/ban (seconds)
8  createdAt: Date;
9}
10
11interface ModeratorToolsProps {
12  server: Server;
13  targetMember: Member;
14  currentUserId: string;
15  onKick: (reason: string) => void;
16  onBan: (reason: string, duration?: number) => void;
17  onTimeout: (duration: number, reason: string) => void;
18}
19
20export default function ModeratorTools({
21  server,
22  targetMember,
23  currentUserId,
24  onKick,
25  onBan,
26  onTimeout
27}: ModeratorToolsProps) {
28  const [action, setAction] = useState<'kick' | 'ban' | 'timeout' | null>(null);
29  const [reason, setReason] = useState('');
30  const [duration, setDuration] = useState(3600); // 1 hour default
31
32  const currentMember = server.members.find(m => m.userId === currentUserId);
33  if (!currentMember) return null;
34
35  const canKick = hasPermission(currentMember, PermissionBits.KICK_MEMBERS, server.roles);
36  const canBan = hasPermission(currentMember, PermissionBits.BAN_MEMBERS, server.roles);
37
38  return (
39    <div className="bg-gray-800 rounded-lg p-4">
40      <h3 className="font-bold text-white mb-4">Moderator Actions</h3>
41
42      <div className="space-y-2 mb-4">
43        {canKick && (
44          <button
45            onClick={() => setAction('kick')}
46            className="w-full py-2 bg-orange-600 hover:bg-orange-700 text-white rounded font-semibold"
47          >
48            Kick from Server
49          </button>
50        )}
51
52        {canBan && (
53          <>
54            <button
55              onClick={() => setAction('ban')}
56              className="w-full py-2 bg-red-600 hover:bg-red-700 text-white rounded font-semibold"
57            >
58              Ban from Server
59            </button>
60
61            <button
62              onClick={() => setAction('timeout')}
63              className="w-full py-2 bg-yellow-600 hover:bg-yellow-700 text-white rounded font-semibold"
64            >
65              Timeout
66            </button>
67          </>
68        )}
69      </div>
70
71      {action && (
72        <div className="border-t border-gray-700 pt-4">
73          <h4 className="text-white font-semibold mb-3">
74            {action === 'kick' && 'Kick Member'}
75            {action === 'ban' && 'Ban Member'}
76            {action === 'timeout' && 'Timeout Member'}
77          </h4>
78
79          {action === 'timeout' && (
80            <div className="mb-3">
81              <label className="block text-sm text-gray-300 mb-2">
82                Duration
83              </label>
84              <select
85                value={duration}
86                onChange={(e) => setDuration(parseInt(e.target.value))}
87                className="w-full bg-gray-900 text-white px-3 py-2 rounded"
88              >
89                <option value={60}>60 seconds</option>
90                <option value={300}>5 minutes</option>
91                <option value={600}>10 minutes</option>
92                <option value={3600}>1 hour</option>
93                <option value={86400}>1 day</option>
94                <option value={604800}>1 week</option>
95              </select>
96            </div>
97          )}
98
99          <div className="mb-3">
100            <label className="block text-sm text-gray-300 mb-2">
101              Reason
102            </label>
103            <textarea
104              value={reason}
105              onChange={(e) => setReason(e.target.value)}
106              className="w-full bg-gray-900 text-white px-3 py-2 rounded resize-none"
107              rows={3}
108            />
109          </div>
110
111          <div className="flex gap-2">
112            <button
113              onClick={() => {
114                if (action === 'kick') onKick(reason);
115                if (action === 'ban') onBan(reason);
116                if (action === 'timeout') onTimeout(duration, reason);
117                setAction(null);
118                setReason('');
119              }}
120              disabled={!reason.trim()}
121              className="flex-1 py-2 bg-red-600 hover:bg-red-700 disabled:bg-gray-600 text-white rounded font-semibold"
122            >
123              Confirm
124            </button>
125            <button
126              onClick={() => setAction(null)}
127              className="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded"
128            >
129              Cancel
130            </button>
131          </div>
132        </div>
133      )}
134    </div>
135  );
136}

Summary 🎓

Permission system - bitwise permissions like Discord ✅ Role hierarchy - position determines priority ✅ Role manager - create, edit, delete roles ✅ Role colors - custom colors in member list ✅ Hoisted roles - separate display in member list ✅ Channel overrides - per-channel permission overrides ✅ Moderation tools - kick, ban, timeout ✅ Audit log - track moderator actions ✅ Permission checks - hasPermission helper

You now have a complete Discord clone with servers, voice, roles, and moderation! 🎮

See you next time! 🚀

Go to CodeWorlds