$ git clone https://ion.nu/git/webchat
commit 7413a5a00cdacead1cc176ee1a54b6a9be0c7229
Author: Alicia <...>
Date:   Thu Dec 31 20:28:31 2015 +0100

    Added option to deregister a channel you're the owner of (have the PRIV_PASSWORD moderator privilege for)

diff --git a/chat.css b/chat.css
index c7de655..4391be2 100644
--- a/chat.css
+++ b/chat.css
@@ -283,3 +283,7 @@ button#setchanpass_button
 {
   display:none; /* Start hidden */
 }
+button#deregisterchannel_button
+{
+  display:none; /* Start hidden */
+}
diff --git a/chat.php b/chat.php
index 918f68d..4d416af 100644
--- a/chat.php
+++ b/chat.php
@@ -30,9 +30,11 @@
         <button onclick="connection.send('logout');">Log out</button>
         <button onclick="var p=prompt('New account password?');if(p)connection.send('setuserpass:'+p);">Change password</button><br />
         <button onclick="connection.send('registerchannel');" id="registerchannel">Register channel</button>
+        <!-- TODO: Move the following into a fullscreen "Channel options" menu? -->
         <button onclick="setupmodmanager(); showfullscreen('managemods', false);" id="managemods_button">Manage moderators</button>
         <button onclick="setupbanlist(); showfullscreen('banlist', false);" id="banlist_button">Banlist</button>
         <button onclick="var p=prompt('New channel password? (empty for no password)');if(p)connection.send('setchanpass:'+p);" id="setchanpass_button">Set channel password</button>
+        <button onclick="if(confirm('Are you sure you want to deregister the channel? Channel password, bans and mods will all be lost'))connection.send('deregisterchannel');" id="deregisterchannel_button">Deregister channel</button>
         <br />
       </div>
       <button onclick="broadcast();" id="broadcastbutton">Broadcast</button><br />
diff --git a/proto.js b/proto.js
index fad673e..ddc5f6a 100644
--- a/proto.js
+++ b/proto.js
@@ -216,8 +216,10 @@ function handlecommands(data)
       if(user.modprivileges&PRIV_PASSWORD)
       {
         document.getElementById('setchanpass_button').style.display='inline-block';
+        document.getElementById('deregisterchannel_button').style.display='inline-block';
       }else{
         document.getElementById('setchanpass_button').style.display='none';
+        document.getElementById('deregisterchannel_button').style.display='none';
       }
     }
   }
diff --git a/src/channels.c b/src/channels.c
index ba91313..06d39a5 100644
--- a/src/channels.c
+++ b/src/channels.c
@@ -194,3 +194,33 @@ void channel_free(struct channel* channel)
     }
   }
 }
+
+char channel_deregister(struct channel* channel)
+{
+  if(!db_removechannel(channel->name)){return 0;} // If something went wrong, at least keep the channel and its mods as is so it can be attempted again
+  unsigned int i;
+  // Clear bans
+  for(i=0; i<channel->bancount; ++i)
+  {
+    free((void*)channel->bans[i].addr);
+    free((void*)channel->bans[i].nickname);
+  }
+  channel->bans=0;
+  channel->bancount=0;
+  // Unset password (if there was one)
+  free(channel->password);
+  channel->password=0;
+  // Clear mod privileges for users
+  for(i=0; i<channel->usercount; ++i)
+  {
+    if(channel->users[i]->modprivileges)
+    {
+      channel->users[i]->modprivileges=0;
+      user_sendmodmsg(channel->users[i]);
+    }
+  }
+  // Notify that the channel is once again free to claim
+  channel->id=-1;
+  channel_write(channel, "channelstatus:unregistered", 26, WEBSOCK_TEXT, 0);
+  return 1;
+}
diff --git a/src/channels.h b/src/channels.h
index 30ace31..1529c3a 100644
--- a/src/channels.h
+++ b/src/channels.h
@@ -43,3 +43,4 @@ extern void channel_ban(struct channel* channel, struct user* user);
 extern char channel_unban(struct channel* channel, unsigned int id);
 extern char channel_checkban(struct channel* channel, struct sockaddr* useraddr);
 extern void channel_free(struct channel* channel);
+extern char channel_deregister(struct channel* channel);
diff --git a/src/chat.c b/src/chat.c
index 373fc5f..2d07668 100644
--- a/src/chat.c
+++ b/src/chat.c
@@ -602,6 +602,18 @@ printf("Got data from user %u (length: %llu, opcode %u): '%s'\n", i, head.length
             websock_write(users[i]->socket, "err:Failed to save password", 27, WEBSOCK_TEXT, use_tls);
           }
         }
+        else if(!strcmp(data, "deregisterchannel"))
+        {
+          if(!(users[i]->modprivileges&PRIV_PASSWORD))
+          {
+            websock_write(users[i]->socket, "err:Insufficient moderator privileges", 37, WEBSOCK_TEXT, use_tls);
+            continue;
+          }
+          if(!channel_deregister(users[i]->channel))
+          {
+            websock_write(users[i]->socket, "err:Failed to deregister channel", 32, WEBSOCK_TEXT, use_tls);
+          }
+        }
       }
       else if(head.opcode==WEBSOCK_BINARY)
       {
diff --git a/src/db.h b/src/db.h
index d347eac..decadfd 100644
--- a/src/db.h
+++ b/src/db.h
@@ -19,9 +19,8 @@
 #define PRIV_TOPIC       4 // Set the channel topic
 #define PRIV_KICK        8 // Kick or ban trolls/creeps
 #define PRIV_SETMODS    16 // Add/remove mods, set privileges
-#define PRIV_PASSWORD 32 // Change the channel password (TODO: Override entrance for mods? only some mods?)
+#define PRIV_PASSWORD   32 // Change the channel password (TODO: Override entrance for mods? only some mods?)
 #define PRIV_ALL        63
-#define PRIV_NONOWNER   15 // For simplicity's sake, start with just distinguishing owner (who can set mods) and non-owner mods
 // Idea: privilege to temporarily mod users with e.g. PLAYMEDIA, CLOSECAM, TOPIC and KICK privileges
 
 extern void db_init(void);