From cd6ad75ee655d4c4c3695d4fc0c8e5e8350cf38c Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Thu, 12 Sep 2019 13:59:57 +1000
Subject: [PATCH 1/4] Added new moderator polling.

---
 Signal/src/Loki/LokiGroupChatPoller.swift     | 20 ++++-------
 .../src/Loki/API/LokiGroupChatAPI.swift       | 34 +++++++++++++++++++
 2 files changed, 41 insertions(+), 13 deletions(-)

diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift
index 767a01c9c..4a0b5108f 100644
--- a/Signal/src/Loki/LokiGroupChatPoller.swift
+++ b/Signal/src/Loki/LokiGroupChatPoller.swift
@@ -4,14 +4,14 @@ public final class LokiGroupChatPoller : NSObject {
     private let group: LokiGroupChat
     private var pollForNewMessagesTimer: Timer? = nil
     private var pollForDeletedMessagesTimer: Timer? = nil
-    private var pollForModerationPermissionTimer: Timer? = nil
+    private var pollForModeratorsTimer: Timer? = nil
     private var hasStarted = false
     private let userHexEncodedPublicKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey
     
     // MARK: Settings
     private let pollForNewMessagesInterval: TimeInterval = 4
     private let pollForDeletedMessagesInterval: TimeInterval = 20
-    private let pollForModerationPermissionInterval: TimeInterval = 10 * 60
+    private let pollForModeratorsInterval: TimeInterval = 10 * 60
     
     // MARK: Lifecycle
     @objc(initForGroup:)
@@ -24,18 +24,18 @@ public final class LokiGroupChatPoller : NSObject {
         if hasStarted { return }
         pollForNewMessagesTimer = Timer.scheduledTimer(withTimeInterval: pollForNewMessagesInterval, repeats: true) { [weak self] _ in self?.pollForNewMessages() }
         pollForDeletedMessagesTimer = Timer.scheduledTimer(withTimeInterval: pollForDeletedMessagesInterval, repeats: true) { [weak self] _ in self?.pollForDeletedMessages() }
-        pollForModerationPermissionTimer = Timer.scheduledTimer(withTimeInterval: pollForModerationPermissionInterval, repeats: true) { [weak self] _ in self?.pollForModerationPermission() }
+        pollForModeratorsTimer = Timer.scheduledTimer(withTimeInterval: pollForModeratorsInterval, repeats: true) { [weak self] _ in self?.pollForModerators() }
         // Perform initial updates
         pollForNewMessages()
         pollForDeletedMessages()
-        pollForModerationPermission()
+        pollForModerators()
         hasStarted = true
     }
     
     @objc public func stop() {
         pollForNewMessagesTimer?.invalidate()
         pollForDeletedMessagesTimer?.invalidate()
-        pollForModerationPermissionTimer?.invalidate()
+        pollForModeratorsTimer?.invalidate()
         hasStarted = false
     }
     
@@ -140,13 +140,7 @@ public final class LokiGroupChatPoller : NSObject {
         }
     }
     
-    private func pollForModerationPermission() {
-        let group = self.group
-        let _ = LokiGroupChatAPI.userHasModerationPermission(for: group.serverID, on: group.server).done { isModerator in
-            let storage = OWSPrimaryStorage.shared()
-            storage.dbReadWriteConnection.readWrite { transaction in
-                storage.setIsModerator(isModerator, for: UInt(group.serverID), on: group.server, in: transaction)
-            }
-        }
+    private func pollForModerators() {
+        let _ = LokiGroupChatAPI.getModerators(for: self.group.serverID, on: self.group.server)
     }
 }
diff --git a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift
index 3bc8e638d..4fdae54a6 100644
--- a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift
+++ b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift
@@ -13,6 +13,14 @@ public final class LokiGroupChatAPI : NSObject {
     @objc public static let publicChatMessageType = "network.loki.messenger.publicChat"
     @objc public static let publicChatServerID: UInt64 = 1
     
+    // Mark: Moderators
+    typealias ModeratorArray = Set<String>
+    typealias ChannelDictionary = [UInt64 : ModeratorArray]
+    typealias ServerMapping = [String : ChannelDictionary]
+    
+    // A mapping from server to channel to moderator
+    private static var moderators = ServerMapping()
+    
     // MARK: Convenience
     private static var userDisplayName: String {
         return SSKEnvironment.shared.contactsManager.displayName(forPhoneIdentifier: userHexEncodedPublicKey) ?? "Anonymous"
@@ -251,6 +259,32 @@ public final class LokiGroupChatAPI : NSObject {
         }
     }
     
+    public static func isUserModerator(user hexEncodedPublicString: String, for group: UInt64, on server: String) -> Bool {
+        return self.moderators[server]?[group]?.contains(hexEncodedPublicString) ?? false
+    }
+    
+    public static func getModerators(for group: UInt64, on server: String) -> Promise<Set<String>> {
+        let url = URL(string: "\(server)/loki/v1/channel/\(group)/get_moderators")!
+        let request = TSRequest(url: url)
+        return TSNetworkManager.shared().makePromise(request: request).map { $0.responseObject }.map { rawResponse in
+            guard let json = rawResponse as? JSON, let moderators = json["moderators"] as? [String] else {
+                print("[Loki] Couldn't parse moderators for group chat with ID: \(group) on server: \(server) from: \(rawResponse).")
+                throw Error.parsingFailed
+            }
+            
+            let moderatorSet = Set(moderators);
+            
+            // Update our cache
+            if (self.moderators.keys.contains(server)) {
+                self.moderators[server]![group] = moderatorSet
+            } else {
+                self.moderators[server] = [group : moderatorSet]
+            }
+            
+            return moderatorSet
+        }
+    }
+    
     // MARK: Public API (Obj-C)
     @objc(getMessagesForGroup:onServer:)
     public static func objc_getMessages(for group: UInt64, on server: String) -> AnyPromise {

From f0fda330debcb6ae28e728593336c7a25178e116 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Thu, 12 Sep 2019 14:09:35 +1000
Subject: [PATCH 2/4] Removed old moderator logic.

---
 .../ConversationView/ConversationViewItem.m            | 10 ++++++----
 SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift   |  1 +
 .../src/Loki/Crypto/OWSPrimaryStorage+Loki.h           |  2 --
 .../src/Loki/Crypto/OWSPrimaryStorage+Loki.m           | 10 ----------
 4 files changed, 7 insertions(+), 16 deletions(-)

diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m
index e3a575d1c..319bde986 100644
--- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m
+++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m
@@ -1236,6 +1236,10 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
     return NO;
 }
 
+- (NSString *)ourHexEncodedPublicKey {
+    return OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
+}
+
 - (BOOL)userCanDeleteGroupMessage
 {
     if (!self.isGroupThread) return false;
@@ -1254,10 +1258,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
     
     // Only allow deletion on incoming messages if the user has moderation permission
     if (interationType == OWSInteractionType_IncomingMessage) {
-        __block BOOL isModerator;
-        [[self primaryStorage].dbReadWriteConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
-            isModerator = [[self primaryStorage] isModeratorForGroup:LKGroupChatAPI.publicChatServerID onServer:LKGroupChatAPI.publicChatServer in:transaction];
-        }];
+        BOOL isModerator = [LKGroupChatAPI isUserModerator:self.ourHexEncodedPublicKey forGroup:LKGroupChatAPI.publicChatServerID onServer: LKGroupChatAPI.publicChatServer];
+    
         if (!isModerator) return false;
     }
 
diff --git a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift
index 4fdae54a6..be01d346f 100644
--- a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift
+++ b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift
@@ -259,6 +259,7 @@ public final class LokiGroupChatAPI : NSObject {
         }
     }
     
+    @objc (isUserModerator:forGroup:onServer:)
     public static func isUserModerator(user hexEncodedPublicString: String, for group: UInt64, on server: String) -> Bool {
         return self.moderators[server]?[group]?.contains(hexEncodedPublicString) ?? false
     }
diff --git a/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.h b/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.h
index 528b70213..6b8487039 100644
--- a/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.h
+++ b/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.h
@@ -99,8 +99,6 @@ NS_ASSUME_NONNULL_BEGIN
 
 - (void)setIDForMessageWithServerID:(NSUInteger)serverID to:(NSString *)messageID in:(YapDatabaseReadWriteTransaction *)transaction;
 - (NSString *_Nullable)getIDForMessageWithServerID:(NSUInteger)serverID in:(YapDatabaseReadTransaction *)transaction;
-- (void)setIsModerator:(BOOL)isModerator forGroup:(NSUInteger)group onServer:(NSString *)server in:(YapDatabaseReadWriteTransaction *)transaction NS_SWIFT_NAME(setIsModerator(_:for:on:in:));
-- (BOOL)isModeratorForGroup:(NSUInteger)group onServer:(NSString *)server in:(YapDatabaseReadTransaction *)transaction NS_SWIFT_NAME(isModerator(for:on:in:));
 
 @end
 
diff --git a/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.m b/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.m
index fa89c1386..dded713a4 100644
--- a/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.m
+++ b/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.m
@@ -176,14 +176,4 @@
     return [transaction objectForKey:key inCollection:LKMessageIDCollection];
 }
 
-- (void)setIsModerator:(BOOL)isModerator forGroup:(NSUInteger)group onServer:(NSString *)server in:(YapDatabaseReadWriteTransaction *)transaction {
-    NSString *key = [NSString stringWithFormat:@"%@.%@", server, @(group)];
-    [transaction setBool:isModerator forKey:key inCollection:LKModerationPermissionCollection];
-}
-
-- (BOOL)isModeratorForGroup:(NSUInteger)group onServer:(NSString *)server in:(YapDatabaseReadTransaction *)transaction NS_SWIFT_NAME(isModerator(for:on:in:)) {
-    NSString *key = [NSString stringWithFormat:@"%@.%@", server, @(group)];
-    return [transaction boolForKey:key inCollection:LKModerationPermissionCollection defaultValue:false];
-}
-
 @end

From af39b35da712fd24c20acf9640705700bd6e6bfc Mon Sep 17 00:00:00 2001
From: Niels Andriesse <andriesseniels@gmail.com>
Date: Thu, 12 Sep 2019 15:06:20 +1000
Subject: [PATCH 3/4] Clean & implement moderator tags UI

---
 .../Crown.imageset/Contents.json              |  12 +++++
 .../Images.xcassets/Crown.imageset/crown.pdf  | Bin 0 -> 1802 bytes
 Signal/src/Loki/LokiGroupChatPoller.swift     |   2 +-
 .../ConversationView/Cells/OWSMessageCell.m   |  23 ++++++++
 .../ConversationView/ConversationViewItem.m   |   5 +-
 .../src/Loki/API/LokiGroupChatAPI.swift       |  51 +++++-------------
 6 files changed, 51 insertions(+), 42 deletions(-)
 create mode 100644 Signal/Images.xcassets/Crown.imageset/Contents.json
 create mode 100644 Signal/Images.xcassets/Crown.imageset/crown.pdf

diff --git a/Signal/Images.xcassets/Crown.imageset/Contents.json b/Signal/Images.xcassets/Crown.imageset/Contents.json
new file mode 100644
index 000000000..97010b1ad
--- /dev/null
+++ b/Signal/Images.xcassets/Crown.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "crown.pdf"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
\ No newline at end of file
diff --git a/Signal/Images.xcassets/Crown.imageset/crown.pdf b/Signal/Images.xcassets/Crown.imageset/crown.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..5e5c424ca7b0782bf0bb5c82a1d06ed4690c386c
GIT binary patch
literal 1802
zcmY!laB<T$)HC5y-THRjZ!QxB1BLvgEG`=x1%02?y!4U`1yi6<5SN02g1%d3PDyGJ
zkj_agNp(q0&QD3@va{nVE-6Y)%;l<>(;DX8Cu`1g=e5W#2a!&tua<6o0wxL74<l7}
z+D}-d;`O)c*-e87FO#}MChBa8|0MYTpT`e>p`Wi0|9#Bhq`x-cX!DUP-k<*0eXsu~
z`}g~QndNdGHF~yRqW-qO7kPEizIOFOv0H7^oV&QPFGxr6Trp1I+o}Fse}49Y76!rm
zpzoIL^J-S<PO%Hnw41u3ejQ_EjnI*4HVNyPgYw=z5Mz0>KKa6plf^tcl`NhgaJ6~d
zcTMd{-(!Q2fP%E2FPE}SFz2}x`cffA%O+v|^}@javmIA05}UZ_Qu396!fPo(9hEZE
z1>2N@(k?i$?x;)=vgo(o%ETR)uypFw+6E4Ghdw3N%+$w||88t}&(ZqF_0BnW<*C(-
zD;DXl{1MEMl{xKBOqBEEBT2hvC&usAanliIPcpi<^FgVPx}l$DN5+cGM7@Py&0bx~
zV$WL<EUopz<<-JzcBKb8*^j*pt@z%w^Vw6!ndPq|Dpp*+lw`ub^4``3T)lViW*rpW
zs$lu6(PQ%$<@LR5MUG$E%91CUp<R&>$MpQEV4#UK!#WwQj^2+77EeU7T>6TeTw+)B
zR^9B_cum?dmq}i@`ltN-_8+%r{L)(D<kBf6;$`)9HP3D?`O}l)ysv*=@3r!1-ttNF
zXBFmOmsoPWZCzdY>+QdTgvu{=biP{qVw%SG7t1nk&s^3ze^XxitT^?<XCE&7ZQ{vk
z_^Ec*v{W&>)yv+^m1w(Y8=H3e%cXlad>FSmwWozBr?>L)epOX0T(5L{)s%3-X*tzj
z+T^Xu%vajGJye{mxXJ(0&u0M)H3lC%t~ejcdbjJqD-j>3*Did=c3ms2k?o$gV4`KS
zU$er5S#4X>--@&@`!ikDU9Ry=ZSw8|vXcuV6sO!<vPLrZTX9TR!SO0rSECy#vf|kv
z&RqOuU$JGQuW8O-UE}hi`tvzQ_QmdynD_fW|JOg~RHp<k&@u2;-X^K^YYvmMRN)E_
zdrt9rnvc4#zA+KB>x=wqdRVt-a%kA>Wecz0T=H`7h0g4@%B9Bn%AXair+j_#f17-@
z<lDt_xXV1O9}2Te%72~t@!0J9n|91}(6v5tM*poh&*7zu@95lKlzns0$2WNwzh2!}
zy)!qD|C?Fpdda={pYQ1YYX5kuZk6OQe^b7mx0!8G&MUb%s~<aX2;6q4U!}`!cJM|^
zM406J6}5?xapkAEUQ4)6y!CA>|E%rrFDL%H-DvGDzi(Hd{C8Up-8W{>Uw7~M_Uzs?
z<DBwY^*jGFUM?`c$d#Ix0?n#G7ATX0ayEzt=50$O3y?gBXACa_z?t5)qQpJ81eoC!
zY-~URK%k#!0OTm>J3A^ED(ELC0!d&o0F(fdKm;|%2yTvoen@3Os)Bw%VtT5As)8Zd
z+<?TQ)Vva)R#@rco0^iD=#*cf5DgSEP%t$#0^?Y)YUljC5}@*81yBJ6DUIBV@=FVl
z90johEDCdHFwo|r#Ju7HkX^}@Fkw%HlA_X77|$7~$|bcdGdVTL-3cTJ@r!;?YH@yP
zQF3ar0w_d+kbDMmGpvwA_^cSDSKm7`r5NY~kmrLGU~Vr3c@)V2Gb957it<xRlYzym
zMsi|iQNDtqo}rnZfr3UxNl8Jml|C?dfuiX}i3J&%$;Ep4Md_NFV2hlAg>Fe^ex3^`
zYBXG|j0_Ad4J-|f3`|VTj7+06k*qUEvJT>2=fsl4ocwgKL7-SEhQ<RT_9}`})3^*2
zEVvBdK*7w^)Yw!ZO#v!qXlxAhCI~3x!Nm*>fiZ+8W?+J$&cMJBnEKGw837|6P0ZL5
z=z26UQv(ZxSV>W0W=?7mxW)?3tV#ts859pe`S~RZAdf>5i)UV1z5*!N!SPsJl2}v%
R_MxGvnE{uos;j>n7XbR6sZ9U?

literal 0
HcmV?d00001

diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift
index 4a0b5108f..78fb92019 100644
--- a/Signal/src/Loki/LokiGroupChatPoller.swift
+++ b/Signal/src/Loki/LokiGroupChatPoller.swift
@@ -141,6 +141,6 @@ public final class LokiGroupChatPoller : NSObject {
     }
     
     private func pollForModerators() {
-        let _ = LokiGroupChatAPI.getModerators(for: self.group.serverID, on: self.group.server)
+        let _ = LokiGroupChatAPI.getModerators(for: group.serverID, on: group.server)
     }
 }
diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m
index b71d39867..1203f9216 100644
--- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m
+++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m
@@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
 @property (nonatomic) OWSMessageBubbleView *messageBubbleView;
 @property (nonatomic) NSLayoutConstraint *messageBubbleViewBottomConstraint;
 @property (nonatomic) AvatarImageView *avatarView;
+@property (nonatomic) UIImageView *moderatorIconView;
 @property (nonatomic, nullable) LKFriendRequestView *friendRequestView;
 @property (nonatomic, nullable) UIImageView *sendFailureBadgeView;
 
@@ -61,6 +62,11 @@ NS_ASSUME_NONNULL_BEGIN
     [self.avatarView autoSetDimension:ALDimensionWidth toSize:self.avatarSize];
     [self.avatarView autoSetDimension:ALDimensionHeight toSize:self.avatarSize];
 
+    self.moderatorIconView = [[UIImageView alloc] init];
+    [self.moderatorIconView autoSetDimension:ALDimensionWidth toSize:20.f];
+    [self.moderatorIconView autoSetDimension:ALDimensionHeight toSize:20.f];
+    self.moderatorIconView.hidden = YES;
+    
     self.messageBubbleViewBottomConstraint = [self.messageBubbleView autoPinBottomToSuperviewMarginWithInset:0];
 
     self.contentView.userInteractionEnabled = YES;
@@ -227,6 +233,11 @@ NS_ASSUME_NONNULL_BEGIN
             [self.messageBubbleView autoPinLeadingToTrailingEdgeOfView:self.avatarView offset:8],
             [self.messageBubbleView autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.avatarView],
         ]];
+        
+        [self.viewConstraints addObjectsFromArray:@[
+            [self.moderatorIconView autoPinEdge:ALEdgeTrailing toEdge:ALEdgeTrailing ofView:self.avatarView],
+            [self.moderatorIconView autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.avatarView withOffset:3.5]
+        ]];
     }
 }
 
@@ -285,6 +296,15 @@ NS_ASSUME_NONNULL_BEGIN
                                                   diameter:self.avatarSize] build];
     self.avatarView.image = authorAvatarImage;
     [self.contentView addSubview:self.avatarView];
+    
+    if (self.viewItem.isGroupThread && !self.viewItem.isRSSFeed) {
+        BOOL isModerator = [LKGroupChatAPI isUserModerator:incomingMessage.authorId forGroup:LKGroupChatAPI.publicChatServerID onServer:LKGroupChatAPI.publicChatServer];
+        UIImage *moderatorIcon = [UIImage imageNamed:@"Crown"];
+        self.moderatorIconView.image = moderatorIcon;
+        self.moderatorIconView.hidden = !isModerator;
+    }
+    
+    [self.contentView addSubview:self.moderatorIconView];
 
     [[NSNotificationCenter defaultCenter] addObserver:self
                                              selector:@selector(otherUsersProfileDidChange:)
@@ -385,6 +405,9 @@ NS_ASSUME_NONNULL_BEGIN
     self.avatarView.image = nil;
     [self.avatarView removeFromSuperview];
 
+    self.moderatorIconView.image = nil;
+    [self.moderatorIconView removeFromSuperview];
+    
     [self.sendFailureBadgeView removeFromSuperview];
     self.sendFailureBadgeView = nil;
 
diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m
index 319bde986..650ac105b 100644
--- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m
+++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m
@@ -1236,7 +1236,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
     return NO;
 }
 
-- (NSString *)ourHexEncodedPublicKey {
+- (NSString *)userHexEncodedPublicKey {
     return OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
 }
 
@@ -1258,8 +1258,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
     
     // Only allow deletion on incoming messages if the user has moderation permission
     if (interationType == OWSInteractionType_IncomingMessage) {
-        BOOL isModerator = [LKGroupChatAPI isUserModerator:self.ourHexEncodedPublicKey forGroup:LKGroupChatAPI.publicChatServerID onServer: LKGroupChatAPI.publicChatServer];
-    
+        BOOL isModerator = [LKGroupChatAPI isUserModerator:self.userHexEncodedPublicKey forGroup:LKGroupChatAPI.publicChatServerID onServer: LKGroupChatAPI.publicChatServer];
         if (!isModerator) return false;
     }
 
diff --git a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift
index be01d346f..94abf6588 100644
--- a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift
+++ b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift
@@ -4,23 +4,17 @@ import PromiseKit
 public final class LokiGroupChatAPI : NSObject {
     private static let storage = OWSPrimaryStorage.shared()
     
+    private static var moderators: [String:[UInt64:Set<String>]] = [:] // Server URL to (channel ID to set of moderator IDs)
+    
     // MARK: Settings
     private static let fallbackBatchCount = 40
     private static let maxRetryCount: UInt = 4
     
     // MARK: Public Chat
-    @objc public static let publicChatServer = "https://chat.lokinet.org"
+    @objc public static let publicChatServer = "https://chat-dev.lokinet.org"
     @objc public static let publicChatMessageType = "network.loki.messenger.publicChat"
     @objc public static let publicChatServerID: UInt64 = 1
     
-    // Mark: Moderators
-    typealias ModeratorArray = Set<String>
-    typealias ChannelDictionary = [UInt64 : ModeratorArray]
-    typealias ServerMapping = [String : ChannelDictionary]
-    
-    // A mapping from server to channel to moderator
-    private static var moderators = ServerMapping()
-    
     // MARK: Convenience
     private static var userDisplayName: String {
         return SSKEnvironment.shared.contactsManager.displayName(forPhoneIdentifier: userHexEncodedPublicKey) ?? "Anonymous"
@@ -244,26 +238,6 @@ public final class LokiGroupChatAPI : NSObject {
         }
     }
     
-    public static func userHasModerationPermission(for group: UInt64, on server: String) -> Promise<Bool> {
-        return getAuthToken(for: server).then { token -> Promise<Bool> in
-            let url = URL(string: "\(server)/loki/v1/user_info")!
-            let request = TSRequest(url: url)
-            request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
-            return TSNetworkManager.shared().makePromise(request: request).map { $0.responseObject }.map { rawResponse in
-                guard let json = rawResponse as? JSON, let data = json["data"] as? JSON else {
-                    print("[Loki] Couldn't parse moderation permission for group chat with ID: \(group) on server: \(server) from: \(rawResponse).")
-                    throw Error.parsingFailed
-                }
-                return data["moderator_status"] as? Bool ?? false
-            }
-        }
-    }
-    
-    @objc (isUserModerator:forGroup:onServer:)
-    public static func isUserModerator(user hexEncodedPublicString: String, for group: UInt64, on server: String) -> Bool {
-        return self.moderators[server]?[group]?.contains(hexEncodedPublicString) ?? false
-    }
-    
     public static func getModerators(for group: UInt64, on server: String) -> Promise<Set<String>> {
         let url = URL(string: "\(server)/loki/v1/channel/\(group)/get_moderators")!
         let request = TSRequest(url: url)
@@ -272,20 +246,21 @@ public final class LokiGroupChatAPI : NSObject {
                 print("[Loki] Couldn't parse moderators for group chat with ID: \(group) on server: \(server) from: \(rawResponse).")
                 throw Error.parsingFailed
             }
-            
-            let moderatorSet = Set(moderators);
-            
-            // Update our cache
-            if (self.moderators.keys.contains(server)) {
-                self.moderators[server]![group] = moderatorSet
+            let moderatorAsSet = Set(moderators);
+            if self.moderators.keys.contains(server) {
+                self.moderators[server]![group] = moderatorAsSet
             } else {
-                self.moderators[server] = [group : moderatorSet]
+                self.moderators[server] = [ group : moderatorAsSet ]
             }
-            
-            return moderatorSet
+            return moderatorAsSet
         }
     }
     
+    @objc (isUserModerator:forGroup:onServer:)
+    public static func isUserModerator(_ hexEncodedPublicString: String, for group: UInt64, on server: String) -> Bool {
+        return moderators[server]?[group]?.contains(hexEncodedPublicString) ?? false
+    }
+    
     // MARK: Public API (Obj-C)
     @objc(getMessagesForGroup:onServer:)
     public static func objc_getMessages(for group: UInt64, on server: String) -> AnyPromise {

From a9926f91c95f63a96cd966a82b8daf1dfd94a73f Mon Sep 17 00:00:00 2001
From: Niels Andriesse <andriesseniels@gmail.com>
Date: Thu, 12 Sep 2019 15:07:15 +1000
Subject: [PATCH 4/4] Undo accidental commit

---
 SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift
index 94abf6588..d6acc9cc0 100644
--- a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift
+++ b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift
@@ -11,7 +11,7 @@ public final class LokiGroupChatAPI : NSObject {
     private static let maxRetryCount: UInt = 4
     
     // MARK: Public Chat
-    @objc public static let publicChatServer = "https://chat-dev.lokinet.org"
+    @objc public static let publicChatServer = "https://chat.lokinet.org"
     @objc public static let publicChatMessageType = "network.loki.messenger.publicChat"
     @objc public static let publicChatServerID: UInt64 = 1