mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-29 00:50:40 +00:00
Add antispam false positive reporting
This commit is contained in:
parent
1750bc5a73
commit
3895daa974
@ -8302,3 +8302,6 @@ Sorry for the inconvenience.";
|
||||
|
||||
"CreateTopic.ShowGeneral" = "Show in Topics";
|
||||
"CreateTopic.ShowGeneralInfo" = "If the 'General' topic is hidden, group members can pull down in the topic list to view it.";
|
||||
|
||||
"Conversation.ContextMenuReportFalsePositive" = "Report False Positive";
|
||||
"Group.AdminLog.AntiSpamFalsePositiveReportedText" = "Telegram moderators will review your report. Thank you!";
|
||||
|
@ -925,18 +925,20 @@ public struct PremiumConfiguration {
|
||||
|
||||
public struct AntiSpamBotConfiguration {
|
||||
public static var defaultValue: AntiSpamBotConfiguration {
|
||||
return AntiSpamBotConfiguration(antiSpamBotId: nil)
|
||||
return AntiSpamBotConfiguration(antiSpamBotId: nil, minimumGroupParticipants: 100)
|
||||
}
|
||||
|
||||
public let antiSpamBotId: EnginePeer.Id?
|
||||
public let minimumGroupParticipants: Int32
|
||||
|
||||
fileprivate init(antiSpamBotId: EnginePeer.Id?) {
|
||||
fileprivate init(antiSpamBotId: EnginePeer.Id?, minimumGroupParticipants: Int32) {
|
||||
self.antiSpamBotId = antiSpamBotId
|
||||
self.minimumGroupParticipants = minimumGroupParticipants
|
||||
}
|
||||
|
||||
public static func with(appConfiguration: AppConfiguration) -> AntiSpamBotConfiguration {
|
||||
if let data = appConfiguration.data, let string = data["telegram_antispam_user_id"] as? String, let value = Int64(string) {
|
||||
return AntiSpamBotConfiguration(antiSpamBotId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(value)))
|
||||
if let data = appConfiguration.data, let botIdString = data["telegram_antispam_user_id"] as? String, let botIdValue = Int64(botIdString), let groupSize = data["telegram_antispam_group_size_min"] as? Double {
|
||||
return AntiSpamBotConfiguration(antiSpamBotId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(botIdValue)), minimumGroupParticipants: Int32(groupSize))
|
||||
} else {
|
||||
return .defaultValue
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import SwiftSignalKit
|
||||
|
||||
public struct ChannelAdminEventLogEntry: Comparable {
|
||||
public let stableId: UInt32
|
||||
public let headerStableId: UInt32
|
||||
public let event: AdminLogEvent
|
||||
public let peers: [PeerId: Peer]
|
||||
|
||||
@ -85,6 +86,7 @@ public final class ChannelAdminEventLogContext {
|
||||
private var filter: ChannelAdminEventLogFilter = ChannelAdminEventLogFilter()
|
||||
|
||||
private var nextStableId: UInt32 = 1
|
||||
private var headerStableIds: [AdminLogEventId: UInt32] = [:]
|
||||
private var stableIds: [AdminLogEventId: UInt32] = [:]
|
||||
|
||||
private var entries: ([ChannelAdminEventLogEntry], ChannelAdminEventLogFilter) = ([], ChannelAdminEventLogFilter())
|
||||
@ -182,13 +184,13 @@ public final class ChannelAdminEventLogContext {
|
||||
}
|
||||
|
||||
var entries: [ChannelAdminEventLogEntry] = events.map { event in
|
||||
return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), event: event, peers: result.peers)
|
||||
return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), headerStableId: strongSelf.headerStableIdForEventId(event.id), event: event, peers: result.peers)
|
||||
}
|
||||
entries.append(contentsOf: strongSelf.entries.0)
|
||||
strongSelf.entries = (entries, strongSelf.filter)
|
||||
} else {
|
||||
let entries: [ChannelAdminEventLogEntry] = events.map { event in
|
||||
return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), event: event, peers: result.peers)
|
||||
return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), headerStableId: strongSelf.headerStableIdForEventId(event.id), event: event, peers: result.peers)
|
||||
}
|
||||
strongSelf.entries = (entries, strongSelf.filter)
|
||||
}
|
||||
@ -214,4 +216,15 @@ public final class ChannelAdminEventLogContext {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
private func headerStableIdForEventId(_ id: AdminLogEventId) -> UInt32 {
|
||||
if let value = self.headerStableIds[id] {
|
||||
return value
|
||||
} else {
|
||||
let value = self.nextStableId
|
||||
self.nextStableId += 1
|
||||
self.headerStableIds[id] = value
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -292,6 +292,10 @@ public extension TelegramEngine {
|
||||
return _internal_toggleAntiSpamProtection(account: self.account, peerId: peerId, enabled: enabled)
|
||||
}
|
||||
|
||||
public func reportAntiSpamFalsePositive(peerId: PeerId, messageId: MessageId) -> Signal<Bool, NoError> {
|
||||
return _internal_reportAntiSpamFalsePositive(account: self.account, peerId: peerId, messageId: messageId)
|
||||
}
|
||||
|
||||
public func requestPeerPhotos(peerId: PeerId) -> Signal<[TelegramPeerPhoto], NoError> {
|
||||
return _internal_requestPeerPhotos(postbox: self.account.postbox, network: self.account.network, peerId: peerId)
|
||||
}
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AntiSpam.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AntiSpam.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "reportfalse_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
135
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AntiSpam.imageset/reportfalse_24.pdf
vendored
Normal file
135
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AntiSpam.imageset/reportfalse_24.pdf
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 4.834961 3.335003 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
5.861066 15.587957 m
|
||||
6.196899 15.711591 6.498980 15.822798 6.742460 15.912901 c
|
||||
6.938185 15.972766 7.061687 15.999998 7.169050 15.999998 c
|
||||
7.276096 15.999998 7.389237 15.973757 7.604651 15.910002 c
|
||||
8.743544 15.512181 11.235205 14.579237 12.364463 14.129102 c
|
||||
12.722498 13.984460 12.842218 13.892902 12.896966 13.823884 c
|
||||
12.934976 13.775966 13.000000 13.665576 13.000000 13.307275 c
|
||||
13.000000 7.185324 l
|
||||
13.000000 6.553703 12.915850 6.062992 12.747086 5.640941 c
|
||||
12.579326 5.221397 12.313388 4.833261 11.900326 4.426327 c
|
||||
11.044427 3.583125 9.640606 2.731597 7.372818 1.412622 c
|
||||
7.318006 1.381764 7.263755 1.358576 7.218547 1.344114 c
|
||||
7.193927 1.336239 7.177481 1.332685 7.169050 1.331110 c
|
||||
7.160619 1.332685 7.144172 1.336239 7.119552 1.344114 c
|
||||
7.074736 1.358451 7.021033 1.381364 6.966707 1.411822 c
|
||||
4.705894 2.743835 3.299533 3.594983 2.437976 4.437263 c
|
||||
2.022268 4.843668 1.754397 5.229964 1.585310 5.647222 c
|
||||
1.415345 6.066645 1.330000 6.554857 1.330000 7.185324 c
|
||||
1.330000 13.307275 l
|
||||
1.330000 13.662965 1.395993 13.779277 1.436774 13.830870 c
|
||||
1.491529 13.900139 1.610740 13.992070 1.957460 14.125925 c
|
||||
1.957469 14.125902 l
|
||||
1.962718 14.127980 l
|
||||
2.859330 14.482901 4.598611 15.123198 5.860996 15.587932 c
|
||||
5.861066 15.587957 l
|
||||
h
|
||||
7.169050 17.329998 m
|
||||
6.842114 17.329998 6.548457 17.244995 6.332937 17.178432 c
|
||||
6.321291 17.174837 6.309747 17.170919 6.298316 17.166689 c
|
||||
6.058186 17.077799 5.755209 16.966265 5.415772 16.841309 c
|
||||
5.415587 16.841240 l
|
||||
4.154459 16.376986 2.390144 15.727495 1.475762 15.365632 c
|
||||
1.086233 15.215069 0.680177 15.018452 0.393381 14.655631 c
|
||||
0.091951 14.274296 0.000000 13.813374 0.000000 13.307275 c
|
||||
0.000000 7.185324 l
|
||||
0.000000 6.433676 0.101960 5.766401 0.352673 5.147715 c
|
||||
0.604264 4.526862 0.990442 3.992426 1.508224 3.486230 c
|
||||
2.512969 2.503967 4.089810 1.563107 6.296909 0.262775 c
|
||||
6.296864 0.262699 l
|
||||
6.306323 0.257332 l
|
||||
6.547542 0.120480 6.865578 0.000000 7.169050 0.000000 c
|
||||
7.472521 0.000000 7.790557 0.120480 8.031776 0.257332 c
|
||||
8.031796 0.257298 l
|
||||
8.037958 0.260883 l
|
||||
10.255331 1.550500 11.831186 2.491209 12.833723 3.478873 c
|
||||
13.350101 3.987590 13.733150 4.524757 13.982018 5.147135 c
|
||||
14.229882 5.767005 14.330000 6.434830 14.330000 7.185324 c
|
||||
14.330000 13.307275 l
|
||||
14.330000 13.810762 14.241129 14.269478 13.938952 14.650421 c
|
||||
13.653583 15.010178 13.247056 15.207094 12.860974 15.362955 c
|
||||
12.858298 15.364022 l
|
||||
11.707717 15.822705 9.179629 16.769213 8.027948 17.170942 c
|
||||
8.018206 17.174341 8.008386 17.177513 7.998495 17.180454 c
|
||||
7.995981 17.181202 l
|
||||
7.783853 17.244291 7.495684 17.329998 7.169050 17.329998 c
|
||||
h
|
||||
7.165000 12.829998 m
|
||||
7.532269 12.829998 7.830000 12.532268 7.830000 12.164998 c
|
||||
7.830000 7.664998 l
|
||||
7.830000 7.297729 7.532269 6.999998 7.165000 6.999998 c
|
||||
6.797730 6.999998 6.500000 7.297729 6.500000 7.664998 c
|
||||
6.500000 12.164998 l
|
||||
6.500000 12.532268 6.797730 12.829998 7.165000 12.829998 c
|
||||
h
|
||||
8.165000 5.164998 m
|
||||
8.165000 4.612713 7.717285 4.164998 7.165000 4.164998 c
|
||||
6.612715 4.164998 6.165000 4.612713 6.165000 5.164998 c
|
||||
6.165000 5.717283 6.612715 6.164998 7.165000 6.164998 c
|
||||
7.717285 6.164998 8.165000 5.717283 8.165000 5.164998 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
3222
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000003312 00000 n
|
||||
0000003335 00000 n
|
||||
0000003508 00000 n
|
||||
0000003582 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
3641
|
||||
%%EOF
|
@ -24,6 +24,8 @@ import UndoUI
|
||||
import TelegramCallsUI
|
||||
import WallpaperBackgroundNode
|
||||
import BotPaymentsUI
|
||||
import ContextUI
|
||||
import Pasteboard
|
||||
|
||||
private final class ChatRecentActionsListOpaqueState {
|
||||
let entries: [ChatRecentActionsEntry]
|
||||
@ -56,13 +58,14 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
private var automaticMediaDownloadSettings: MediaAutoDownloadSettings
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
private var visibleAreaInset = UIEdgeInsets()
|
||||
|
||||
private let backgroundNode: WallpaperBackgroundNode
|
||||
private let panelBackgroundNode: NavigationBackgroundNode
|
||||
private let panelSeparatorNode: ASDisplayNode
|
||||
private let panelButtonNode: HighlightableButtonNode
|
||||
|
||||
private let listNode: ListView
|
||||
fileprivate let listNode: ListView
|
||||
private let loadingNode: ChatLoadingNode
|
||||
private let emptyNode: ChatRecentActionsEmptyNode
|
||||
|
||||
@ -86,6 +89,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
private var adminsDisposable: Disposable?
|
||||
private var adminsState: ChannelMemberListState?
|
||||
private let banDisposables = DisposableDict<PeerId>()
|
||||
private let reportFalsePositiveDisposables = DisposableDict<MessageId>()
|
||||
|
||||
private weak var antiSpamTooltipController: UndoOverlayController?
|
||||
|
||||
@ -258,8 +262,10 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
}, openPeerMention: { [weak self] name in
|
||||
self?.openPeerMention(name)
|
||||
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, _, location in
|
||||
self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame, location: location)
|
||||
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
||||
let gesture: ContextGesture? = anyRecognizer as? ContextGesture
|
||||
self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame, recognizer: recognizer, gesture: gesture, location: location)
|
||||
}, openMessageReactionContextMenu: { _, _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in
|
||||
}, activateMessagePinch: { _ in
|
||||
@ -628,6 +634,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
self.resolvePeerByNameDisposable.dispose()
|
||||
self.adminsDisposable?.dispose()
|
||||
self.banDisposables.dispose()
|
||||
self.reportFalsePositiveDisposables.dispose()
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
@ -664,6 +671,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
transition.updateFrame(node: self.panelSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
||||
transition.updateFrame(node: self.panelButtonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: intrinsicPanelHeight)))
|
||||
|
||||
self.visibleAreaInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: panelHeight, right: 0.0)
|
||||
|
||||
transition.updateBounds(node: self.listNode, bounds: CGRect(origin: CGPoint(), size: layout.size))
|
||||
transition.updatePosition(node: self.listNode, position: CGRect(origin: CGPoint(), size: layout.size).center)
|
||||
|
||||
@ -819,15 +828,51 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}))
|
||||
}
|
||||
|
||||
private func openMessageContextMenu(message: Message, selectAll: Bool, node: ASDisplayNode, frame: CGRect, location: CGPoint?) {
|
||||
var actions: [ContextMenuAction] = []
|
||||
private func openMessageContextMenu(message: Message, selectAll: Bool, node: ASDisplayNode, frame: CGRect, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil, location: CGPoint? = nil) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
self.dismissAllTooltips()
|
||||
|
||||
let context = self.context
|
||||
let source: ContextContentSource
|
||||
if let location = location {
|
||||
source = .location(ChatMessageContextLocationContentSource(controller: controller, location: node.view.convert(node.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
|
||||
} else {
|
||||
source = .extracted(ChatRecentActionsMessageContextExtractedContentSource(controllerNode: self, message: message, selectAll: selectAll))
|
||||
}
|
||||
|
||||
var actions: [ContextMenuItem] = []
|
||||
if !message.text.isEmpty {
|
||||
actions.append(ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: {
|
||||
UIPasteboard.general.string = message.text
|
||||
|
||||
let content: UndoOverlayContent = .copy(text: self.presentationData.strings.Conversation_TextCopied)
|
||||
self.presentController(UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), .current, nil)
|
||||
}))
|
||||
actions.append(
|
||||
.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuCopy, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
if let strongSelf = self {
|
||||
var messageEntities: [MessageTextEntity]?
|
||||
var restrictedText: String?
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||
messageEntities = attribute.entities
|
||||
}
|
||||
if let attribute = attribute as? RestrictedContentMessageAttribute {
|
||||
restrictedText = attribute.platformText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
if let restrictedText = restrictedText {
|
||||
storeMessageTextInPasteboard(restrictedText, entities: nil)
|
||||
} else {
|
||||
storeMessageTextInPasteboard(message.text, entities: messageEntities)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(0.2, {
|
||||
let content: UndoOverlayContent = .copy(text: strongSelf.presentationData.strings.Conversation_TextCopied)
|
||||
strongSelf.presentController(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), .current, nil)
|
||||
})
|
||||
}
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
if let author = message.author, let adminsState = self.adminsState {
|
||||
@ -847,42 +892,54 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
if canBan {
|
||||
actions.append(ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuBan, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuBan), action: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.banDisposables.set((strongSelf.context.engine.peers.fetchChannelParticipant(peerId: strongSelf.peer.id, participantId: author.id)
|
||||
|> deliverOnMainQueue).start(next: { participant in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentController(channelBannedMemberController(context: strongSelf.context, peerId: strongSelf.peer.id, memberId: author.id, initialParticipant: participant, updated: { _ in }, upgradedToSupergroup: { _, f in f() }), .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
}), forKey: author.id)
|
||||
}
|
||||
}))
|
||||
actions.append(
|
||||
.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuBan, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
if let strongSelf = self {
|
||||
f(.default)
|
||||
strongSelf.banDisposables.set((strongSelf.context.engine.peers.fetchChannelParticipant(peerId: strongSelf.peer.id, participantId: author.id)
|
||||
|> deliverOnMainQueue).start(next: { participant in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentController(channelBannedMemberController(context: strongSelf.context, peerId: strongSelf.peer.id, memberId: author.id, initialParticipant: participant, updated: { _ in }, upgradedToSupergroup: { _, f in f() }), .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
}), forKey: author.id)
|
||||
}
|
||||
}))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if !actions.isEmpty {
|
||||
let contextMenuController = ContextMenuController(actions: actions)
|
||||
|
||||
self.controllerInteraction.highlightedState = ChatInterfaceHighlightedState(messageStableId: message.stableId)
|
||||
self.updateItemNodesHighlightedStates(animated: true)
|
||||
|
||||
contextMenuController.dismissed = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.controllerInteraction.highlightedState?.messageStableId == message.stableId {
|
||||
strongSelf.controllerInteraction.highlightedState = nil
|
||||
strongSelf.updateItemNodesHighlightedStates(animated: true)
|
||||
}
|
||||
let configuration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||
for peer in message.peers {
|
||||
if peer.0 == configuration.antiSpamBotId {
|
||||
if !actions.isEmpty {
|
||||
actions.insert(.separator, at: 0)
|
||||
}
|
||||
actions.insert(
|
||||
.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuReportFalsePositive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AntiSpam"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
if let strongSelf = self {
|
||||
strongSelf.reportFalsePositiveDisposables.set((strongSelf.context.engine.peers.reportAntiSpamFalsePositive(peerId: message.id.peerId, messageId: message.id)
|
||||
|> deliverOnMainQueue).start(), forKey: message.id)
|
||||
|
||||
Queue.mainQueue().after(0.2, {
|
||||
let content: UndoOverlayContent = .image(image: UIImage(bundleImageName: "Chat/AntiSpamTooltipIcon")!, title: nil, text: strongSelf.presentationData.strings.Group_AdminLog_AntiSpamFalsePositiveReportedText, undo: false)
|
||||
strongSelf.presentController(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), .current, nil)
|
||||
})
|
||||
}
|
||||
})), at: 0
|
||||
)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
self.presentController(contextMenuController, .window(.root), ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak node] in
|
||||
if let strongSelf = self, let node = node {
|
||||
return (node, frame, strongSelf, strongSelf.bounds)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
guard !actions.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(actions))), recognizer: recognizer, gesture: gesture)
|
||||
controller.window?.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
private func updateItemNodesHighlightedStates(animated: Bool) {
|
||||
@ -1043,4 +1100,70 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func frameForVisibleArea() -> CGRect {
|
||||
let rect = CGRect(origin: CGPoint(x: self.visibleAreaInset.left, y: self.visibleAreaInset.top), size: CGSize(width: self.bounds.size.width - self.visibleAreaInset.left - self.visibleAreaInset.right, height: self.bounds.size.height - self.visibleAreaInset.top - self.visibleAreaInset.bottom))
|
||||
|
||||
return rect
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatRecentActionsMessageContextExtractedContentSource: ContextExtractedContentSource {
|
||||
let keepInPlace: Bool = false
|
||||
let ignoreContentTouches: Bool = false
|
||||
let blurBackground: Bool = true
|
||||
|
||||
private weak var controllerNode: ChatRecentActionsControllerNode?
|
||||
private let message: Message
|
||||
private let selectAll: Bool
|
||||
|
||||
var shouldBeDismissed: Signal<Bool, NoError> {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
init(controllerNode: ChatRecentActionsControllerNode, message: Message, selectAll: Bool) {
|
||||
self.controllerNode = controllerNode
|
||||
self.message = message
|
||||
self.selectAll = selectAll
|
||||
}
|
||||
|
||||
func takeView() -> ContextControllerTakeViewInfo? {
|
||||
guard let controllerNode = self.controllerNode else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result: ContextControllerTakeViewInfo?
|
||||
controllerNode.listNode.forEachItemNode { itemNode in
|
||||
guard let itemNode = itemNode as? ChatMessageItemView else {
|
||||
return
|
||||
}
|
||||
guard let item = itemNode.item else {
|
||||
return
|
||||
}
|
||||
if item.content.contains(where: { $0.0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode(stableId: self.selectAll ? nil : self.message.stableId) {
|
||||
result = ContextControllerTakeViewInfo(containingItem: .node(contentNode), contentAreaInScreenSpace: controllerNode.convert(controllerNode.frameForVisibleArea(), to: nil))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func putBack() -> ContextControllerPutBackViewInfo? {
|
||||
guard let controllerNode = self.controllerNode else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result: ContextControllerPutBackViewInfo?
|
||||
controllerNode.listNode.forEachItemNode { itemNode in
|
||||
guard let itemNode = itemNode as? ChatMessageItemView else {
|
||||
return
|
||||
}
|
||||
guard let item = itemNode.item else {
|
||||
return
|
||||
}
|
||||
if item.content.contains(where: { $0.0.stableId == self.message.stableId }) {
|
||||
result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: controllerNode.convert(controllerNode.frameForVisibleArea(), to: nil))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
}, to: &text, entities: &entities)
|
||||
}
|
||||
let action = TelegramMediaActionType.customText(text: text, entities: entities)
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||
case .content:
|
||||
let peers = SimpleDictionary<PeerId, Peer>()
|
||||
@ -182,7 +182,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
}, to: &text, entities: &entities)
|
||||
}
|
||||
let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities)
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||
case .content:
|
||||
var previousAttributes: [MessageAttribute] = []
|
||||
@ -232,7 +232,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
}, to: &text, entities: &entities)
|
||||
}
|
||||
let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities)
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||
case .content:
|
||||
var previousAttributes: [MessageAttribute] = []
|
||||
@ -374,7 +374,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
}, to: &text, entities: &entities)
|
||||
|
||||
let action = TelegramMediaActionType.customText(text: text, entities: entities)
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||
case .content:
|
||||
if let message = message {
|
||||
@ -459,7 +459,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
|
||||
let action = TelegramMediaActionType.customText(text: text, entities: entities)
|
||||
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||
case .content:
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
@ -502,7 +502,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
|
||||
let action = TelegramMediaActionType.customText(text: text, entities: entities)
|
||||
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||
case .content:
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
@ -526,6 +526,9 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let peer = self.entry.peers[self.entry.event.peerId] {
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||
}
|
||||
@ -1072,7 +1075,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
|
||||
let action = TelegramMediaActionType.customText(text: text, entities: entities)
|
||||
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||
case .content:
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
@ -1634,7 +1637,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
}, to: &text, entities: &entities)
|
||||
|
||||
let action = TelegramMediaActionType.customText(text: text, entities: entities)
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||
case .content:
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
|
Loading…
x
Reference in New Issue
Block a user