Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,11 @@
#import <SSignalKit/SSignalKit.h>
@class TGBridgeMediaAttachment;
@interface TGBridgeAudioSignals : NSObject
+ (SSignal *)audioForAttachment:(TGBridgeMediaAttachment *)attachment conversationId:(int64_t)conversationId messageId:(int32_t)messageId;
+ (SSignal *)sentAudioForConversationId:(int64_t)conversationId;
@end
@@ -0,0 +1,162 @@
#import "TGBridgeAudioSignals.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeClient.h"
#import "TGFileCache.h"
#import "TGExtensionDelegate.h"
#import <libkern/OSAtomic.h>
@interface TGBridgeAudioManager : NSObject
{
NSMutableArray *_pendingUrls;
OSSpinLock _pendingUrlsLock;
}
@end
@implementation TGBridgeAudioManager
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_pendingUrls = [[NSMutableArray alloc] init];
}
return self;
}
- (void)addUrl:(NSString *)url
{
OSSpinLockLock(&_pendingUrlsLock);
[_pendingUrls addObject:url];
OSSpinLockUnlock(&_pendingUrlsLock);
}
- (void)removeUrl:(NSString *)url
{
OSSpinLockLock(&_pendingUrlsLock);
[_pendingUrls removeObject:url];
OSSpinLockUnlock(&_pendingUrlsLock);
}
- (bool)hasUrl:(NSString *)url
{
OSSpinLockLock(&_pendingUrlsLock);
bool contains = [_pendingUrls containsObject:url];
OSSpinLockUnlock(&_pendingUrlsLock);
return contains;
}
@end
@implementation TGBridgeAudioSignals
+ (SSignal *)audioForAttachment:(TGBridgeMediaAttachment *)attachment conversationId:(int64_t)conversationId messageId:(int32_t)messageId
{
NSString *url = [NSString stringWithFormat:@"audio_%lld_%d", conversationId, messageId];
SSignal *remoteSignal = [[[[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeAudioSubscription alloc] initWithAttachment:attachment peerId:conversationId messageId:messageId]] onDispose:^
{
// cancel download
}] mapToSignal:^SSignal *(__unused id next)
{
return [self _downloadedFileWithUrl:url];
}];
return [[self _cachedOrPendingWithUrl:url] catch:^SSignal *(id error)
{
return remoteSignal;
}];
}
+ (SSignal *)_loadCachedWithUrl:(NSString *)url
{
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
TGFileCache *audioCache = [TGExtensionDelegate instance].audioCache;
if ([audioCache hasDataForKey:url])
{
[subscriber putNext:[audioCache urlForKey:url]];
[subscriber putCompletion];
}
else
{
[subscriber putError:nil];
}
return nil;
}];
}
+ (SSignal *)_downloadedFileWithUrl:(NSString *)url
{
return [[self _loadCachedWithUrl:url] catch:^SSignal *(id error)
{
return [[[TGBridgeClient instance] fileSignalForKey:url] take:1];
}];
}
+ (SSignal *)_cachedOrPendingWithUrl:(NSString *)url
{
return [[self _loadCachedWithUrl:url] catch:^SSignal *(id error)
{
if ([[self audioManager] hasUrl:url])
return [self _downloadedFileWithUrl:url];
return [SSignal fail:nil];
}];
}
+ (SSignal *)sentAudioForConversationId:(int64_t)conversationId
{
return [[[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeAudioSentSubscription alloc] initWithConversationId:conversationId]] onNext:^(TGBridgeMessage *next)
{
int64_t identifier = 0;
int64_t localIdentifier = 0;
for (TGBridgeMediaAttachment *attachment in next.media)
{
if ([attachment isKindOfClass:[TGBridgeAudioMediaAttachment class]])
{
identifier = ((TGBridgeAudioMediaAttachment *)attachment).audioId;
localIdentifier = ((TGBridgeAudioMediaAttachment *)attachment).localAudioId;
}
else if ([attachment isKindOfClass:[TGBridgeDocumentMediaAttachment class]])
{
identifier = ((TGBridgeDocumentMediaAttachment *)attachment).documentId;
localIdentifier = ((TGBridgeDocumentMediaAttachment *)attachment).localDocumentId;
}
}
if (identifier != 0 && localIdentifier != 0)
{
TGFileCache *audioCache = [[TGExtensionDelegate instance] audioCache];
NSString *localId = [NSString stringWithFormat:@"%lld", localIdentifier];
NSString *audioId = [NSString stringWithFormat:@"%lld", identifier];
if ([audioCache hasDataForKey:localId] && ![audioCache hasDataForKey:audioId])
{
NSURL *localUrl = [audioCache urlForKey:localId];
NSURL *remoteUrl = [audioCache urlForKey:audioId];
[[NSFileManager defaultManager] moveItemAtURL:localUrl toURL:remoteUrl error:nil];
}
}
}];
}
+ (TGBridgeAudioManager *)audioManager
{
static dispatch_once_t onceToken;
static TGBridgeAudioManager *manager;
dispatch_once(&onceToken, ^
{
manager = [[TGBridgeAudioManager alloc] init];
});
return manager;
}
@end
@@ -0,0 +1,46 @@
#import <Foundation/Foundation.h>
@class TGBridgeMessage;
@interface TGBridgeBotReplyMarkupButton : NSObject <NSCoding>
{
NSString *_text;
}
@property (nonatomic, readonly) NSString *text;
- (instancetype)initWithText:(NSString *)text;
@end
@interface TGBridgeBotReplyMarkupRow : NSObject <NSCoding>
{
NSArray *_buttons;
}
@property (nonatomic, readonly) NSArray *buttons;
- (instancetype)initWithButtons:(NSArray *)buttons;
@end
@interface TGBridgeBotReplyMarkup : NSObject <NSCoding>
{
int32_t _userId;
int32_t _messageId;
TGBridgeMessage *_message;
bool _hideKeyboardOnActivation;
bool _alreadyActivated;
NSArray *_rows;
}
@property (nonatomic, readonly) int32_t userId;
@property (nonatomic, readonly) int32_t messageId;
@property (nonatomic, readonly) TGBridgeMessage *message;
@property (nonatomic, readonly) bool hideKeyboardOnActivation;
@property (nonatomic, readonly) bool alreadyActivated;
@property (nonatomic, readonly) NSArray *rows;
@end
@@ -0,0 +1,101 @@
#import "TGBridgeBotReplyMarkup.h"
NSString *const TGBridgeBotReplyMarkupButtonText = @"text";
@implementation TGBridgeBotReplyMarkupButton
- (instancetype)initWithText:(NSString *)text
{
self = [super init];
if (self != nil)
{
_text = text;
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self != nil)
{
_text = [aDecoder decodeObjectForKey:TGBridgeBotReplyMarkupButtonText];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.text forKey:TGBridgeBotReplyMarkupButtonText];
}
@end
NSString *const TGBridgeBotReplyMarkupRowButtons = @"buttons";
@implementation TGBridgeBotReplyMarkupRow
- (instancetype)initWithButtons:(NSArray *)buttons
{
self = [super init];
if (self != nil)
{
_buttons = buttons;
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self != nil)
{
_buttons = [aDecoder decodeObjectForKey:TGBridgeBotReplyMarkupRowButtons];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.buttons forKey:TGBridgeBotReplyMarkupRowButtons];
}
@end
NSString *const TGBridgeBotReplyMarkupUserId = @"userId";
NSString *const TGBridgeBotReplyMarkupMessageId = @"messageId";
NSString *const TGBridgeBotReplyMarkupMessage = @"message";
NSString *const TGBridgeBotReplyMarkupHideKeyboardOnActivation = @"hideKeyboardOnActivation";
NSString *const TGBridgeBotReplyMarkupAlreadyActivated = @"alreadyActivated";
NSString *const TGBridgeBotReplyMarkupRows = @"rows";
@implementation TGBridgeBotReplyMarkup
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self != nil)
{
_userId = [aDecoder decodeInt32ForKey:TGBridgeBotReplyMarkupUserId];
_messageId = [aDecoder decodeInt32ForKey:TGBridgeBotReplyMarkupMessageId];
_message = [aDecoder decodeObjectForKey:TGBridgeBotReplyMarkupMessage];
_hideKeyboardOnActivation = [aDecoder decodeBoolForKey:TGBridgeBotReplyMarkupHideKeyboardOnActivation];
_alreadyActivated = [aDecoder decodeBoolForKey:TGBridgeBotReplyMarkupAlreadyActivated];
_rows = [aDecoder decodeObjectForKey:TGBridgeBotReplyMarkupRows];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeInt32:self.userId forKey:TGBridgeBotReplyMarkupUserId];
[aCoder encodeInt32:self.messageId forKey:TGBridgeBotReplyMarkupMessageId];
[aCoder encodeObject:self.message forKey:TGBridgeBotReplyMarkupMessage];
[aCoder encodeBool:self.hideKeyboardOnActivation forKey:TGBridgeBotReplyMarkupHideKeyboardOnActivation];
[aCoder encodeBool:self.alreadyActivated forKey:TGBridgeBotReplyMarkupAlreadyActivated];
[aCoder encodeObject:self.rows forKey:TGBridgeBotReplyMarkupRows];
}
@end
@@ -0,0 +1,8 @@
#import <SSignalKit/SSignalKit.h>
@interface TGBridgeBotSignals : NSObject
+ (SSignal *)botInfoForUserId:(int32_t)userId;
+ (SSignal *)botReplyMarkupForPeerId:(int64_t)peerId;
@end
@@ -0,0 +1,54 @@
#import "TGBridgeBotSignals.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeUserCache.h"
#import "TGBridgeClient.h"
@implementation TGBridgeBotSignals
+ (SSignal *)botInfoForUserId:(int32_t)userId
{
SSignal *cachedSignal = [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
TGBridgeUser *user = [[TGBridgeUserCache instance] userWithId:userId];
TGBridgeBotInfo *botInfo = [[TGBridgeUserCache instance] botInfoForUserId:userId];
if (botInfo == nil)
{
[subscriber putError:nil];
}
else
{
[subscriber putNext:botInfo];
}
return nil;
}];
return [cachedSignal catch:^SSignal *(__unused id error)
{
return [[[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeUserBotInfoSubscription alloc] initWithUserIds:@[ @(userId) ]]] mapToSignal:^SSignal *(NSDictionary *bots)
{
TGBridgeBotInfo *botInfo = bots[@(userId)];
if (botInfo != nil)
{
[[TGBridgeUserCache instance] storeBotInfo:botInfo forUserId:userId];
return [SSignal single:botInfo];
}
else
{
return [SSignal fail:nil];
}
}];
}];
}
+ (SSignal *)botReplyMarkupForPeerId:(int64_t)peerId
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeBotReplyMarkupSubscription alloc] initWithPeerId:peerId]];
}
@end
@@ -0,0 +1,7 @@
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "WKInterfaceTable+TGDataDrivenTable.h"
@interface TGBridgeChat (TGTableItem) <TGTableItem>
@end
@@ -0,0 +1,10 @@
#import "TGBridgeChat+TGTableItem.h"
@implementation TGBridgeChat (TGTableItem)
- (NSString *)uniqueIdentifier
{
return [NSString stringWithFormat:@"%lld", self.identifier];
}
@end
@@ -0,0 +1,7 @@
#import <SSignalKit/SSignalKit.h>
@interface TGBridgeChatListSignals : NSObject
+ (SSignal *)chatListWithLimit:(NSUInteger)limit;
@end
@@ -0,0 +1,14 @@
#import "TGBridgeChatListSignals.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeClient.h"
@implementation TGBridgeChatListSignals
+ (SSignal *)chatListWithLimit:(NSUInteger)limit;
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeChatListSubscription alloc] initWithLimit:limit]];
}
@end
@@ -0,0 +1,11 @@
#import <SSignalKit/SSignalKit.h>
@interface TGBridgeChatMessageListSignals : NSObject
+ (SSignal *)chatMessageListViewWithPeerId:(int64_t)peerId atMessageId:(int32_t)messageId rangeMessageCount:(NSUInteger)rangeMessageCount;
+ (SSignal *)chatMessageWithPeerId:(int64_t)peerId messageId:(int32_t)messageId;
+ (SSignal *)readChatMessageListWithPeerId:(int64_t)peerId messageId:(int32_t)messageId;
@end
@@ -0,0 +1,24 @@
#import "TGBridgeChatMessageListSignals.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeClient.h"
@implementation TGBridgeChatMessageListSignals
+ (SSignal *)chatMessageListViewWithPeerId:(int64_t)peerId atMessageId:(int32_t)messageId rangeMessageCount:(NSUInteger)rangeMessageCount
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeChatMessageListSubscription alloc] initWithPeerId:peerId atMessageId:messageId rangeMessageCount:rangeMessageCount]];
}
+ (SSignal *)chatMessageWithPeerId:(int64_t)peerId messageId:(int32_t)messageId
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeChatMessageSubscription alloc] initWithPeerId:peerId messageId:messageId]];
}
+ (SSignal *)readChatMessageListWithPeerId:(int64_t)peerId messageId:(int32_t)messageId
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeReadChatMessageListSubscription alloc] initWithPeerId:peerId messageId:messageId]];
}
@end
+33
View File
@@ -0,0 +1,33 @@
#import <SSignalKit/SSignalKit.h>
@class TGBridgeSubscription;
@interface TGBridgeClient : NSObject
- (SSignal *)requestSignalWithSubscription:(TGBridgeSubscription *)subscription;
- (SSignal *)contextSignal;
- (SSignal *)fileSignalForKey:(NSString *)key;
- (NSArray *)stickerPacks;
- (void)handleDidBecomeActive;
- (void)handleWillResignActive;
- (void)sendFileWithURL:(NSURL *)url metadata:(NSDictionary *)metadata;
- (void)updateReachability;
- (bool)isServerReachable;
- (bool)isActuallyReachable;
- (SSignal *)actualReachabilitySignal;
- (SSignal *)reachabilitySignal;
- (SSignal *)userInfoSignal;
- (SSignal *)sendMessageData:(NSData *)messageData;
- (void)sendRawMessageData:(NSData *)messageData replyHandler:(void (^)(NSData *))replyHandler errorHandler:(void (^)(NSError *))errorHandler;
- (void)transferUserInfo:(NSDictionary *)userInfo;
+ (instancetype)instance;
@end
+657
View File
@@ -0,0 +1,657 @@
#import "TGBridgeClient.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGWatchCommon.h"
#import <WatchConnectivity/WatchConnectivity.h>
#import "TGFileCache.h"
#import "TGBridgeStickersSignals.h"
#import "TGBridgePresetsSignals.h"
#import "TGExtensionDelegate.h"
#import <libkern/OSAtomic.h>
NSString *const TGBridgeContextDomain = @"com.telegram.BridgeContext";
const NSTimeInterval TGBridgeClientTimerInterval = 4.0;
const NSTimeInterval TGBridgeClientWakeInterval = 2.0;
@interface TGBridgeClient () <WCSessionDelegate>
{
int32_t _sessionId;
bool _reachable;
bool _processingNotification;
SMulticastSignalManager *_signalManager;
SMulticastSignalManager *_fileSignalManager;
SVariable *_context;
SPipe *_actualReachabilityPipe;
SPipe *_reachabilityPipe;
SPipe *_userInfoPipe;
dispatch_queue_t _contextQueue;
OSSpinLock _outgoingQueueLock;
NSMutableArray *_outgoingMessageQueue;
NSArray *_stickerPacks;
OSSpinLock _stickerPacksLock;
NSMutableDictionary *_subscriptions;
NSTimeInterval _lastForegroundEntry;
STimer *_timer;
bool _sentFirstPing;
bool _isActive;
}
@property (nonatomic, readonly) WCSession *session;
@end
@implementation TGBridgeClient
- (instancetype)init
{
self = [super init];
if (self != nil)
{
int32_t sessionId = 0;
arc4random_buf(&sessionId, sizeof(int32_t));
_sessionId = sessionId;
_contextQueue = dispatch_queue_create(TGBridgeContextDomain.UTF8String, nil);
_signalManager = [[SMulticastSignalManager alloc] init];
_fileSignalManager = [[SMulticastSignalManager alloc] init];
_context = [[SVariable alloc] init];
_userInfoPipe = [[SPipe alloc] init];
_actualReachabilityPipe = [[SPipe alloc] init];
_reachabilityPipe = [[SPipe alloc] init];
_reachable = true;
_outgoingMessageQueue = [[NSMutableArray alloc] init];
_subscriptions = [[NSMutableDictionary alloc] init];
self.session.delegate = self;
[self.session activateSession];
TGLog(@"BridgeClient: initialized");
[self ping];
}
return self;
}
- (void)transferUserInfo:(NSDictionary *)userInfo
{
[self.session transferUserInfo:userInfo];
}
- (SSignal *)requestSignalWithSubscription:(TGBridgeSubscription *)subscription
{
if (!_sentFirstPing)
[self ping];
NSData *messageData = [NSKeyedArchiver archivedDataWithRootObject:subscription];
void (^transcribe)(id, SSubscriber *, bool *) = ^(id message, SSubscriber *subscriber, bool *completed)
{
NSLog(@"BridgeClient: received %p %@", subscription, NSStringFromClass(subscription.class));
TGBridgeResponse *response = nil;
if ([message isKindOfClass:[TGBridgeResponse class]])
{
response = message;
}
else if ([message isKindOfClass:[NSData class]])
{
@try
{
id unarchivedMessage = [NSKeyedUnarchiver unarchiveObjectWithData:message];
if ([unarchivedMessage isKindOfClass:[TGBridgeResponse class]])
response = (TGBridgeResponse *)unarchivedMessage;
}
@catch (NSException *exception)
{
}
}
if (response == nil)
return;
switch (response.type)
{
case TGBridgeResponseTypeNext:
[subscriber putNext:response.next];
break;
case TGBridgeResponseTypeFailed:
[subscriber putError:response.error];
break;
case TGBridgeResponseTypeCompleted:
if (completed != NULL)
*completed = true;
[subscriber putCompletion];
break;
default:
break;
}
};
__weak TGBridgeClient *weakSelf = self;
return [[[[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
NSLog(@"BridgeClient: requestSub %p %@", subscription, NSStringFromClass(subscription.class));
SDisposableSet *combinedDisposable = [[SDisposableSet alloc] init];
SMetaDisposable *currentDisposable = [[SMetaDisposable alloc] init];
__block bool completed = false;
[combinedDisposable add:currentDisposable];
void (^afterSendMessage)(void) = ^
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[combinedDisposable add:[[strongSelf->_signalManager multicastedPipeForKey:[NSString stringWithFormat:@"%lld", subscription.identifier]] startWithNext:^(id next)
{
transcribe(next, subscriber, NULL);
} error:^(id error)
{
[subscriber putError:error];
} completed:^
{
[subscriber putCompletion];
}]];
};
[currentDisposable setDisposable:[[[self sendMessageData:messageData] onStart:^
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf != nil)
strongSelf->_subscriptions[@(subscription.identifier)] = subscription;
}] startWithNext:^(id next)
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf != nil)
transcribe(next, subscriber, &completed);
} error:^(NSError *error)
{
if ([error isKindOfClass:[NSError class]] && error.domain == WCErrorDomain)
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf _enqueueMessage:messageData];
afterSendMessage();
}
else
{
[subscriber putError:error];
}
} completed:^
{
if (completed)
return;
afterSendMessage();
}]];
return combinedDisposable;
}] onCompletion:^
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf->_subscriptions removeObjectForKey:@(subscription.identifier)];
}] onDispose:^
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf != nil)
{
[strongSelf->_subscriptions removeObjectForKey:@(subscription.identifier)];
[strongSelf unsubscribe:subscription.identifier];
}
}];
}
- (void)unsubscribe:(int64_t)identifier
{
TGBridgeDisposal *disposal = [[TGBridgeDisposal alloc] initWithIdentifier:identifier];
NSData *message = [NSKeyedArchiver archivedDataWithRootObject:disposal];
[self.session sendMessageData:message replyHandler:nil errorHandler:^(NSError *error)
{
[self _logError:error];
}];
}
- (SSignal *)sendMessageData:(NSData *)messageData
{
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
[self.session sendMessageData:messageData replyHandler:^(NSData *replyMessageData)
{
if (replyMessageData.length > 0)
[subscriber putNext:replyMessageData];
[subscriber putCompletion];
} errorHandler:^(NSError * _Nonnull error)
{
[self _logError:error];
[subscriber putError:error];
}];
return nil;
}];
}
- (void)sendRawMessageData:(NSData *)messageData replyHandler:(void (^)(NSData *))replyHandler errorHandler:(void (^)(NSError *))errorHandler
{
[self.session sendMessageData:messageData replyHandler:replyHandler errorHandler:errorHandler];
}
#pragma mark -
- (SSignal *)contextSignal
{
return _context.signal;
}
#pragma mark -
- (SSignal *)fileSignalForKey:(NSString *)key
{
return [_fileSignalManager multicastedPipeForKey:key];
}
- (void)sendFileWithURL:(NSURL *)url metadata:(NSDictionary *)metadata
{
[self.session transferFile:url metadata:metadata];
}
#pragma mark -
- (NSArray *)stickerPacks
{
OSSpinLockLock(&_stickerPacksLock);
if (_stickerPacks != nil)
{
NSArray *stickerPacks = [_stickerPacks copy];
OSSpinLockUnlock(&_stickerPacksLock);
return stickerPacks;
}
else
{
NSArray *stickerPacks = [self readStickerPacks];
if (stickerPacks == nil)
stickerPacks = [NSArray array];
_stickerPacks = stickerPacks;
OSSpinLockUnlock(&_stickerPacksLock);
return stickerPacks;
}
}
- (NSArray *)readStickerPacks
{
NSURL *url = [TGBridgeStickersSignals stickerPacksURL];
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
if (data == nil)
return nil;
NSArray *stickerPacks = nil;
@try
{
stickerPacks = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
@catch (NSException *exception)
{
}
if (![stickerPacks isKindOfClass:[NSArray class]])
return nil;
return stickerPacks;
}
#pragma mark -
- (void)session:(WCSession *)session didReceiveMessageData:(NSData *)messageData
{
[self handleReceivedData:messageData replyHandler:nil];
}
- (void)session:(WCSession *)session didReceiveMessageData:(NSData *)messageData replyHandler:(nonnull void (^)(NSData * _Nonnull))replyHandler
{
[self handleReceivedData:messageData replyHandler:replyHandler];
}
- (void)handleReceivedData:(NSData *)messageData replyHandler:(void (^)(NSData *))replyHandler
{
id message = nil;
@try
{
message = [NSKeyedUnarchiver unarchiveObjectWithData:messageData];
}
@catch (NSException *exception)
{
}
if ([message isKindOfClass:[TGBridgeResponse class]])
{
TGBridgeResponse *response = (TGBridgeResponse *)message;
[_signalManager putNext:response toMulticastedPipeForKey:[NSString stringWithFormat:@"%lld", response.subscriptionIdentifier]];
}
else if ([message isKindOfClass:[TGBridgeSubscriptionListRequest class]])
{
[self refreshSubscriptions];
}
else if ([message isKindOfClass:[TGBridgeFile class]])
{
TGBridgeFile *file = (TGBridgeFile *)message;
NSString *type = file.metadata[TGBridgeIncomingFileTypeKey];
NSString *identifier = file.metadata[TGBridgeIncomingFileIdentifierKey];
if (identifier == nil)
return;
if ([type isEqualToString:TGBridgeIncomingFileTypeImage])
{
NSLog(@"Received message image file: %@", identifier);
[[TGExtensionDelegate instance].imageCache cacheData:file.data key:identifier synchronous:true unserializeBlock:^id(NSData *data)
{
return data;
} completion:^(NSURL *url)
{
[_fileSignalManager putNext:url toMulticastedPipeForKey:identifier];
}];
}
}
}
- (void)session:(WCSession *)session didReceiveApplicationContext:(NSDictionary *)applicationContext
{
TGBridgeContext *context = [[TGBridgeContext alloc] initWithDictionary:applicationContext];
[_context set:[SSignal single:context]];
}
- (void)session:(WCSession *)session didReceiveFile:(WCSessionFile *)file
{
NSString *type = file.metadata[TGBridgeIncomingFileTypeKey];
NSString *identifier = file.metadata[TGBridgeIncomingFileIdentifierKey];
if (identifier == nil)
return;
if ([identifier isEqualToString:@"stickers"])
{
NSURL *stickerPacksURL = [TGBridgeStickersSignals stickerPacksURL];
if ([[NSFileManager defaultManager] fileExistsAtPath:stickerPacksURL.path])
[[NSFileManager defaultManager] removeItemAtURL:stickerPacksURL error:nil];
[[NSFileManager defaultManager] moveItemAtURL:file.fileURL toURL:stickerPacksURL error:nil];
NSArray *stickerPacks = [self readStickerPacks];
OSSpinLockLock(&_stickerPacksLock);
_stickerPacks = stickerPacks;
OSSpinLockUnlock(&_stickerPacksLock);
[_fileSignalManager putNext:stickerPacks toMulticastedPipeForKey:identifier];
}
else if ([identifier isEqualToString:@"localization"])
{
[[TGExtensionDelegate instance] setCustomLocalizationFile:file.fileURL];
}
else if ([identifier isEqualToString:@"presets"])
{
NSURL *presetsURL = [TGBridgePresetsSignals presetsURL];
if ([[NSFileManager defaultManager] fileExistsAtPath:presetsURL.path])
[[NSFileManager defaultManager] removeItemAtURL:presetsURL error:nil];
[[NSFileManager defaultManager] moveItemAtURL:file.fileURL toURL:presetsURL error:nil];
}
else if ([type isEqualToString:TGBridgeIncomingFileTypeImage])
{
NSLog(@"Received image file: %@", identifier);
[[TGExtensionDelegate instance].imageCache cacheFileAtURL:file.fileURL key:identifier synchronous:true unserializeBlock:^id(NSData *data)
{
return data;
} completion:^(NSURL *url)
{
[_fileSignalManager putNext:url toMulticastedPipeForKey:identifier];
}];
}
else if ([type isEqualToString:TGBridgeIncomingFileTypeAudio])
{
NSLog(@"Received audio file: %@", identifier);
[[TGExtensionDelegate instance].audioCache cacheFileAtURL:file.fileURL key:identifier synchronous:true unserializeBlock:nil completion:^(NSURL *url)
{
[_fileSignalManager putNext:url toMulticastedPipeForKey:identifier];
}];
}
}
- (void)sessionReachabilityDidChange:(WCSession *)session
{
bool reachable = session.isReachable;
if (!reachable)
{
TGDispatchAfter(4.5, dispatch_get_main_queue(), ^
{
bool newReachable = session.isReachable;
if (newReachable == reachable && newReachable != _reachable)
{
_reachable = newReachable;
_reachabilityPipe.sink(@(newReachable));
}
});
}
else if (_reachable != reachable)
{
_reachable = reachable;
_reachabilityPipe.sink(@(reachable));
[self ping];
}
if (reachable && !_processingNotification)
[self sendQueuedMessages];
}
- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *,id> *)userInfo
{
_userInfoPipe.sink(userInfo);
}
- (void)session:(nonnull WCSession *)session activationDidCompleteWithState:(WCSessionActivationState)activationState error:(nullable NSError *)error {
if (activationState == WCSessionActivationStateActivated) {
TGBridgeContext *context = [[TGBridgeContext alloc] initWithDictionary:session.receivedApplicationContext];
[_context set:[SSignal single:context]];
} else {
TGLog(@"[BridgeClient] inactive session state");
}
}
- (SSignal *)userInfoSignal
{
return _userInfoPipe.signalProducer();
}
#pragma mark -
- (void)_enqueueMessage:(NSData *)message
{
TGLog(@"[BridgeClient] Enqued failed message");
OSSpinLockLock(&_outgoingQueueLock);
[_outgoingMessageQueue addObject:message];
OSSpinLockUnlock(&_outgoingQueueLock);
}
- (void)sendQueuedMessages
{
OSSpinLockLock(&_outgoingQueueLock);
if (_outgoingMessageQueue.count > 0)
{
TGLog(@"[BridgeClient] Sending queued messages");
for (NSData *messageData in _outgoingMessageQueue)
[self.session sendMessageData:messageData replyHandler:nil errorHandler:nil];
[_outgoingMessageQueue removeAllObjects];
}
OSSpinLockUnlock(&_outgoingQueueLock);
}
#pragma mark -
- (void)ping
{
if (!_isActive || _processingNotification)
return;
TGBridgePing *ping = [[TGBridgePing alloc] initWithSessionId:_sessionId];
NSData *message = [NSKeyedArchiver archivedDataWithRootObject:ping];
[self.session sendMessageData:message replyHandler:^(NSData *replyData)
{
_sentFirstPing = true;
} errorHandler:^(NSError *error)
{
[self _logError:error];
}];
}
- (void)refreshSubscriptions
{
NSArray *activeSubscriptions = [_subscriptions allValues];
NSMutableArray *subscriptions = [[NSMutableArray alloc] init];
for (TGBridgeSubscription *subscription in activeSubscriptions)
{
if (subscription.renewable)
[subscriptions addObject:subscription];
}
TGBridgeSubscriptionList *subscriptionsList = [[TGBridgeSubscriptionList alloc] initWithArray:subscriptions];
NSData *message = [NSKeyedArchiver archivedDataWithRootObject:subscriptionsList];
[self.session sendMessageData:message replyHandler:nil errorHandler:^(NSError *error)
{
[self _logError:error];
}];
}
#pragma mark -
- (void)handleDidBecomeActive
{
_isActive = true;
NSTimeInterval currentTime = [[NSDate date] timeIntervalSinceReferenceDate];
if (_lastForegroundEntry == 0 || currentTime - _lastForegroundEntry > TGBridgeClientWakeInterval)
{
if (_lastForegroundEntry != 0)
[self ping];
_lastForegroundEntry = currentTime;
}
if (_timer == nil)
{
__weak TGBridgeClient *weakSelf = self;
NSTimeInterval interval = _lastForegroundEntry == 0 ? TGBridgeClientTimerInterval : MAX(MIN(TGBridgeClientTimerInterval - currentTime - _lastForegroundEntry, TGBridgeClientTimerInterval), 1);
__block void (^completion)(void) = ^
{
__strong TGBridgeClient *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf ping];
strongSelf->_lastForegroundEntry = [[NSDate date] timeIntervalSinceReferenceDate];
strongSelf->_timer = [[STimer alloc] initWithTimeout:TGBridgeClientTimerInterval repeat:false completion:completion queue:[SQueue mainQueue]];
[strongSelf->_timer start];
};
_timer = [[STimer alloc] initWithTimeout:interval repeat:false completion:completion queue:[SQueue mainQueue]];
[_timer start];
}
}
- (void)handleWillResignActive
{
_isActive = false;
[_timer invalidate];
_timer = nil;
}
#pragma mark -
- (void)updateReachability
{
if (self.session.isReachable && !_reachable)
_reachable = true;
}
- (bool)isServerReachable
{
return _reachable;
}
- (bool)isActuallyReachable
{
return self.session.isReachable;
}
- (SSignal *)actualReachabilitySignal
{
return [[SSignal single:@(self.session.isReachable)] then:_actualReachabilityPipe.signalProducer()];
}
- (SSignal *)reachabilitySignal
{
return [[SSignal single:@(self.session.isReachable)] then:_reachabilityPipe.signalProducer()];
}
- (void)_logError:(NSError *)error
{
NSLog(@"%@", error);
}
#pragma mark -
- (WCSession *)session
{
return [WCSession defaultSession];
}
+ (instancetype)instance
{
static dispatch_once_t onceToken;
static TGBridgeClient *instance;
dispatch_once(&onceToken, ^
{
instance = [[TGBridgeClient alloc] init];
});
return instance;
}
@end
@@ -0,0 +1,7 @@
#import <SSignalKit/SSignalKit.h>
@interface TGBridgeContactsSignals : NSObject
+ (SSignal *)searchContactsWithQuery:(NSString *)query;
@end
@@ -0,0 +1,14 @@
#import "TGBridgeContactsSignals.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeClient.h"
@implementation TGBridgeContactsSignals
+ (SSignal *)searchContactsWithQuery:(NSString *)query
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeContactsSubscription alloc] initWithQuery:query]];
}
@end
@@ -0,0 +1,7 @@
#import <SSignalKit/SSignalKit.h>
@interface TGBridgeConversationSignals : NSObject
+ (SSignal *)conversationWithPeerId:(int64_t)peerId;
@end
@@ -0,0 +1,14 @@
#import "TGBridgeConversationSignals.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeClient.h"
@implementation TGBridgeConversationSignals
+ (SSignal *)conversationWithPeerId:(int64_t)peerId
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeConversationSubscription alloc] initWithPeerId:peerId]];
}
@end
@@ -0,0 +1,11 @@
#import <SSignalKit/SSignalKit.h>
@interface TGBridgeLocationSignals : NSObject
+ (SSignal *)currentLocation;
+ (SSignal *)nearbyVenuesWithLimit:(NSUInteger)limit;
@end
extern NSString *const TGBridgeLocationAccessRequiredKey;
extern NSString *const TGBridgeLocationLoadingKey;
@@ -0,0 +1,192 @@
#import "TGBridgeLocationSignals.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeClient.h"
#import <CoreLocation/CoreLocation.h>
NSString *const TGBridgeLocationAccessRequiredKey = @"access";
NSString *const TGBridgeLocationLoadingKey = @"loading";
@interface TGLocationManagerAdapter : NSObject <CLLocationManagerDelegate>
{
CLLocationManager *_locationManager;
}
@property (nonatomic, copy) void (^authorizationStatusChanged)(TGLocationManagerAdapter *sender, CLAuthorizationStatus status);
@property (nonatomic, copy) void (^locationChanged)(CLLocation *location);
@end
@implementation TGLocationManagerAdapter
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
_locationManager.distanceFilter = 20;
//_locationManager.activityType = CLActivityTypeOther;
}
return self;
}
- (void)dealloc
{
_locationManager.delegate = nil;
}
- (void)requestAuthorizationWithCompletion:(void (^)(TGLocationManagerAdapter *, CLAuthorizationStatus ))completion
{
self.authorizationStatusChanged = completion;
CLAuthorizationStatus status = [self authorizationStatus];
if (status == kCLAuthorizationStatusNotDetermined || status == kCLAuthorizationStatusAuthorizedWhenInUse)
[_locationManager requestAlwaysAuthorization];
else
self.authorizationStatusChanged(self, [self authorizationStatus]);
}
- (CLAuthorizationStatus)authorizationStatus
{
return [CLLocationManager authorizationStatus];
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
if (status != kCLAuthorizationStatusNotDetermined && self.authorizationStatusChanged != nil)
self.authorizationStatusChanged(self, status);
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *location = locations.lastObject;
if (self.locationChanged != nil)
self.locationChanged(location);
}
- (void)startUpdating
{
[_locationManager requestLocation];
}
- (void)stopUpdating
{
[_locationManager stopUpdatingLocation];
}
@end
@implementation TGBridgeLocationSignals
+ (SSignal *)currentLocation
{
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
SDisposableSet *compositeDisposable = [[SDisposableSet alloc] init];
TGLocationManagerAdapter *adapter = [[TGLocationManagerAdapter alloc] init];
if (adapter.authorizationStatus == kCLAuthorizationStatusAuthorizedAlways)
{
[adapter startUpdating];
}
else if (adapter.authorizationStatus == kCLAuthorizationStatusNotDetermined || adapter.authorizationStatus == kCLAuthorizationStatusAuthorizedWhenInUse)
{
[subscriber putNext:TGBridgeLocationAccessRequiredKey];
SMetaDisposable *accessDisposable = [[SMetaDisposable alloc] init];
[accessDisposable setDisposable:[[[SSignal complete] delay:1.0f onQueue:[SQueue mainQueue]] startWithNext:nil completed:^
{
[adapter requestAuthorizationWithCompletion:^(TGLocationManagerAdapter *sender, CLAuthorizationStatus status)
{
if (status == kCLAuthorizationStatusAuthorizedAlways)
{
[subscriber putNext:TGBridgeLocationLoadingKey];
[sender startUpdating];
}
else
{
[subscriber putNext:TGBridgeLocationAccessRequiredKey];
}
}];
}]];
[compositeDisposable add:accessDisposable];
}
else if (adapter.authorizationStatus != kCLAuthorizationStatusAuthorizedAlways)
{
[subscriber putNext:TGBridgeLocationAccessRequiredKey];
}
adapter.locationChanged = ^(CLLocation *location)
{
if (location != nil && location.horizontalAccuracy > 0)
{
[subscriber putNext:location];
[subscriber putCompletion];
}
};
SBlockDisposable *adapterDisposable = [[SBlockDisposable alloc] initWithBlock:^
{
[adapter stopUpdating];
}];
[compositeDisposable add:adapterDisposable];
return compositeDisposable;
}];
}
+ (SSignal *)nearbyVenuesWithLimit:(NSUInteger)limit
{
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
SMetaDisposable *disposable = [[SMetaDisposable alloc] init];
[disposable setDisposable:[[[self currentLocation] mapToSignal:^SSignal *(id next)
{
if ([next isKindOfClass:[NSString class]])
{
return [SSignal single:next];
}
else if ([next isKindOfClass:[CLLocation class]])
{
CLLocation *location = (CLLocation *)next;
return [[SSignal single:next] then:[self _nearbyVenuesWithCoordinate:location.coordinate limit:limit]];
}
return nil;
}] startWithNext:^(id next)
{
if ([next isKindOfClass:[NSArray class]])
{
[subscriber putNext:next];
[subscriber putCompletion];
[disposable dispose];
}
else
{
[subscriber putNext:next];
}
}]];
return nil;
}];
}
+ (SSignal *)_nearbyVenuesWithCoordinate:(CLLocationCoordinate2D)coordinate limit:(NSUInteger)limit
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeNearbyVenuesSubscription alloc] initWithCoordinate:coordinate limit:limit]];
}
@end
@@ -0,0 +1,7 @@
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "WKInterfaceTable+TGDataDrivenTable.h"
@interface TGBridgeLocationVenue (TGTableItem) <TGTableItem>
@end
@@ -0,0 +1,10 @@
#import "TGBridgeLocationVenue+TGTableItem.h"
@implementation TGBridgeLocationVenue (TGTableItem)
- (NSString *)uniqueIdentifier
{
return self.identifier;
}
@end
@@ -0,0 +1,23 @@
#import <SSignalKit/SSignalKit.h>
#import <WatchCommonWatch/WatchCommonWatch.h>
@class TGBridgeImageMediaAttachment;
@class TGBridgeVideoMediaAttachment;
@class TGBridgeDocumentMediaAttachment;
typedef enum
{
TGMediaStickerImageTypeList,
TGMediaStickerImageTypeNormal,
TGMediaStickerImageTypeInput
} TGMediaStickerImageType;
@interface TGBridgeMediaSignals : NSObject
+ (SSignal *)thumbnailWithPeerId:(int64_t)peerId messageId:(int32_t)messageId size:(CGSize)size notification:(bool)notification;
+ (SSignal *)avatarWithPeerId:(int64_t)peerId url:(NSString *)url type:(TGBridgeMediaAvatarType)type;
+ (SSignal *)stickerWithDocumentId:(int64_t)documentId packId:(int64_t)packId accessHash:(int64_t)accessHash type:(TGMediaStickerImageType)type;
+ (SSignal *)stickerWithDocumentId:(int64_t)documentId peerId:(int64_t)peerId messageId:(int32_t)messageId type:(TGMediaStickerImageType)type notification:(bool)notification;
@end
@@ -0,0 +1,202 @@
#import "TGBridgeMediaSignals.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeClient.h"
#import "TGFileCache.h"
#import "TGGeometry.h"
#import "TGWatchCommon.h"
#import "TGExtensionDelegate.h"
#import <libkern/OSAtomic.h>
@interface TGBridgeMediaManager : NSObject
{
NSMutableArray *_pendingUrls;
OSSpinLock _pendingUrlsLock;
}
@end
@implementation TGBridgeMediaManager
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_pendingUrls = [[NSMutableArray alloc] init];
}
return self;
}
- (void)addUrl:(NSString *)url
{
if (url == nil)
return;
OSSpinLockLock(&_pendingUrlsLock);
[_pendingUrls addObject:url];
OSSpinLockUnlock(&_pendingUrlsLock);
}
- (void)removeUrl:(NSString *)url
{
if (url == nil)
return;
OSSpinLockLock(&_pendingUrlsLock);
[_pendingUrls removeObject:url];
OSSpinLockUnlock(&_pendingUrlsLock);
}
- (bool)hasUrl:(NSString *)url
{
if (url == nil)
return false;
OSSpinLockLock(&_pendingUrlsLock);
bool contains = [_pendingUrls containsObject:url];
OSSpinLockUnlock(&_pendingUrlsLock);
return contains;
}
@end
@implementation TGBridgeMediaSignals
+ (SSignal *)thumbnailWithPeerId:(int64_t)peerId messageId:(int32_t)messageId size:(CGSize)size notification:(bool)notification
{
TGBridgeSubscription *subscription = [[TGBridgeMediaThumbnailSubscription alloc] initWithPeerId:peerId messageId:messageId size:size notification:notification];
NSString *imageUrl = [NSString stringWithFormat:@"%lld_%d", peerId, messageId];
return [self _requestImageWithUrl:imageUrl subscription:subscription];
}
+ (SSignal *)avatarWithPeerId:(int64_t)peerId url:(NSString *)url type:(TGBridgeMediaAvatarType)type
{
NSString *imageUrl = [NSString stringWithFormat:@"%@_%lu", url, (unsigned long)type];
TGBridgeSubscription *subscription = [[TGBridgeMediaAvatarSubscription alloc] initWithPeerId:peerId url:url type:type];
return [self _requestImageWithUrl:imageUrl subscription:subscription];
}
+ (CGSize)_imageSizeForStickerType:(TGMediaStickerImageType)avatarType
{
switch (avatarType)
{
case TGMediaStickerImageTypeList:
return CGSizeMake(19, 19);
case TGMediaStickerImageTypeNormal:
case TGMediaStickerImageTypeInput:
{
return TGWatchStickerSizeForScreen(TGWatchScreenType());
}
default:
break;
}
return CGSizeMake(72, 72);
}
+ (SSignal *)stickerWithDocumentId:(int64_t)documentId packId:(int64_t)packId accessHash:(int64_t)accessHash type:(TGMediaStickerImageType)type
{
CGSize imageSize = [self _imageSizeForStickerType:type];
NSString *imageUrl = [NSString stringWithFormat:@"sticker_%lld_%dx%d_0", documentId, (int)imageSize.width, (int)imageSize.height];
TGBridgeSubscription *subscription = [[TGBridgeMediaStickerSubscription alloc] initWithDocumentId:documentId stickerPackId:packId stickerPackAccessHash:accessHash stickerPeerId:0 stickerMessageId:0 notification:false size:imageSize];
return [self _requestImageWithUrl:imageUrl subscription:subscription];
}
+ (SSignal *)stickerWithDocumentId:(int64_t)documentId peerId:(int64_t)peerId messageId:(int32_t)messageId type:(TGMediaStickerImageType)type notification:(bool)notification
{
CGSize imageSize = [self _imageSizeForStickerType:type];
NSString *imageUrl = [NSString stringWithFormat:@"sticker_%lld_%dx%d_%d", documentId, (int)imageSize.width, (int)imageSize.height, notification];
TGBridgeSubscription *subscription = [[TGBridgeMediaStickerSubscription alloc] initWithDocumentId:documentId stickerPackId:0 stickerPackAccessHash:0 stickerPeerId:peerId stickerMessageId:messageId notification:notification size:imageSize];
return [self _requestImageWithUrl:imageUrl subscription:subscription];
}
+ (id(^)(NSData *))_imageUnserializeBlock
{
return ^id(NSData *data)
{
return data;
};
}
+ (SSignal *)_requestImageWithUrl:(NSString *)url subscription:(TGBridgeSubscription *)subscription
{
SSignal *remoteSignal = [[[[TGBridgeClient instance] requestSignalWithSubscription:subscription] onStart:^
{
if (![[self mediaManager] hasUrl:url])
[[self mediaManager] addUrl:url];
}] then:[[self _downloadedFileWithUrl:url] onNext:^(id next)
{
[[self mediaManager] removeUrl:url];
}]];
return [[self _cachedOrPendingWithUrl:url] catch:^SSignal *(id error)
{
return remoteSignal;
}];
}
+ (SSignal *)_loadCachedWithUrl:(NSString *)url memoryOnly:(bool)memoryOnly unserializeBlock:(UIImage *(^)(NSData *))unserializeBlock
{
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
[[TGExtensionDelegate instance].imageCache fetchDataForKey:url memoryOnly:memoryOnly synchronous:false unserializeBlock:unserializeBlock completion:^(id image)
{
if (image != nil)
{
[subscriber putNext:image];
[subscriber putCompletion];
}
else
{
[subscriber putError:nil];
}
}];
return nil;
}];
}
+ (SSignal *)_downloadedFileWithUrl:(NSString *)url
{
return [[self _loadCachedWithUrl:url memoryOnly:true unserializeBlock:nil] catch:^SSignal *(id error)
{
return [[[[TGBridgeClient instance] fileSignalForKey:url] take:1] map:^NSData *(NSURL *url)
{
return [NSData dataWithContentsOfURL:url];
}];
}];
}
+ (SSignal *)_cachedOrPendingWithUrl:(NSString *)url
{
return [[self _loadCachedWithUrl:url memoryOnly:false unserializeBlock:[self _imageUnserializeBlock]] catch:^SSignal *(id error)
{
if ([[self mediaManager] hasUrl:url])
return [self _downloadedFileWithUrl:url];
return [SSignal fail:nil];
}];
}
+ (TGBridgeMediaManager *)mediaManager
{
static dispatch_once_t onceToken;
static TGBridgeMediaManager *manager;
dispatch_once(&onceToken, ^
{
manager = [[TGBridgeMediaManager alloc] init];
});
return manager;
}
@end
@@ -0,0 +1,7 @@
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "WKInterfaceTable+TGDataDrivenTable.h"
@interface TGBridgeMessage (TGTableItem) <TGTableItem>
@end
@@ -0,0 +1,10 @@
#import "TGBridgeMessage+TGTableItem.h"
@implementation TGBridgeMessage (TGTableItem)
- (NSString *)uniqueIdentifier
{
return [NSString stringWithFormat:@"%d", self.identifier];
}
@end
@@ -0,0 +1,10 @@
#import <SSignalKit/SSignalKit.h>
@interface TGBridgePeerSettingsSignals : NSObject
+ (SSignal *)peerSettingsWithPeerId:(int64_t)peerId;
+ (SSignal *)toggleMutedWithPeerId:(int64_t)peerId;
+ (SSignal *)updateBlockStatusWithPeerId:(int64_t)peerId blocked:(bool)blocked;
@end
@@ -0,0 +1,24 @@
#import "TGBridgePeerSettingsSignals.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeClient.h"
@implementation TGBridgePeerSettingsSignals
+ (SSignal *)peerSettingsWithPeerId:(int64_t)peerId;
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgePeerSettingsSubscription alloc] initWithPeerId:peerId]];
}
+ (SSignal *)toggleMutedWithPeerId:(int64_t)peerId
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgePeerUpdateNotificationSettingsSubscription alloc] initWithPeerId:peerId]];
}
+ (SSignal *)updateBlockStatusWithPeerId:(int64_t)peerId blocked:(bool)blocked
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgePeerUpdateBlockStatusSubscription alloc] initWithPeerId:peerId blocked:blocked]];
}
@end
@@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>
@interface TGBridgePresetsSignals : NSObject
+ (NSURL *)presetsURL;
@end
@@ -0,0 +1,17 @@
#import "TGBridgePresetsSignals.h"
@implementation TGBridgePresetsSignals
+ (NSURL *)presetsURL
{
static dispatch_once_t onceToken;
static NSURL *presetsURL;
dispatch_once(&onceToken, ^
{
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true)[0];
presetsURL = [[NSURL alloc] initFileURLWithPath:[documentsPath stringByAppendingPathComponent:@"presets.data"]];
});
return presetsURL;
}
@end
@@ -0,0 +1,7 @@
#import <SSignalKit/SSignalKit.h>
@interface TGBridgeRemoteSignals : NSObject
+ (SSignal *)openRemoteMessageWithPeerId:(int64_t)peerId messageId:(int32_t)messageId type:(int32_t)type autoPlay:(bool)autoPlay;
@end
@@ -0,0 +1,15 @@
#import "TGBridgeRemoteSignals.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeClient.h"
@implementation TGBridgeRemoteSignals
+ (SSignal *)openRemoteMessageWithPeerId:(int64_t)peerId messageId:(int32_t)messageId type:(int32_t)type autoPlay:(bool)autoPlay
{
autoPlay = false;
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeRemoteSubscription alloc] initWithPeerId:peerId messageId:messageId type:type autoPlay:autoPlay]];
}
@end
@@ -0,0 +1,15 @@
#import <SSignalKit/SSignalKit.h>
#import <WatchCommonWatch/WatchCommonWatch.h>
@interface TGBridgeSendMessageSignals : NSObject
+ (SSignal *)sendMessageWithPeerId:(int64_t)peerId text:(NSString *)text replyToMid:(int32_t)replyToMid;
+ (SSignal *)sendMessageWithPeerId:(int64_t)peerId location:(TGBridgeLocationMediaAttachment *)location replyToMid:(int32_t)replyToMid;
+ (SSignal *)sendMessageWithPeerId:(int64_t)peerId sticker:(TGBridgeDocumentMediaAttachment *)sticker replyToMid:(int32_t)replyToMid;
+ (SSignal *)forwardMessageWithPeerId:(int64_t)peerId mid:(int32_t)mid targetPeerId:(int64_t)targetPeerId;
@end
@@ -0,0 +1,29 @@
#import "TGBridgeSendMessageSignals.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeClient.h"
@implementation TGBridgeSendMessageSignals
+ (SSignal *)sendMessageWithPeerId:(int64_t)peerId text:(NSString *)text replyToMid:(int32_t)replyToMid
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeSendTextMessageSubscription alloc] initWithPeerId:peerId text:text replyToMid:replyToMid]];
}
+ (SSignal *)sendMessageWithPeerId:(int64_t)peerId location:(TGBridgeLocationMediaAttachment *)location replyToMid:(int32_t)replyToMid
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeSendLocationMessageSubscription alloc] initWithPeerId:peerId location:location replyToMid:replyToMid]];
}
+ (SSignal *)sendMessageWithPeerId:(int64_t)peerId sticker:(TGBridgeDocumentMediaAttachment *)sticker replyToMid:(int32_t)replyToMid
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeSendStickerMessageSubscription alloc] initWithPeerId:peerId document:sticker replyToMid:replyToMid]];
}
+ (SSignal *)forwardMessageWithPeerId:(int64_t)peerId mid:(int32_t)mid targetPeerId:(int64_t)targetPeerId
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeSendForwardedMessageSubscription alloc] initWithPeerId:peerId messageId:mid targetPeerId:targetPeerId]];
}
@end
@@ -0,0 +1,15 @@
#import <SSignalKit/SSignalKit.h>
typedef enum
{
TGBridgeSynchronizationStateSynchronized,
TGBridgeSynchronizationStateWaitingForNetwork,
TGBridgeSynchronizationStateConnecting,
TGBridgeSynchronizationStateUpdating
} TGBridgeSynchronizationStateValue;
@interface TGBridgeStateSignal : NSObject
+ (SSignal *)synchronizationState;
@end
@@ -0,0 +1,20 @@
#import "TGBridgeStateSignal.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeClient.h"
@implementation TGBridgeStateSignal
+ (SSignal *)synchronizationState
{
return [[[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeStateSubscription alloc] init]] map:^NSNumber *(id next)
{
if ([next isKindOfClass:[NSNumber class]])
return next;
return @(TGBridgeSynchronizationStateSynchronized);
}];
}
@end
@@ -0,0 +1,14 @@
#import <WatchCommonWatch/WatchCommonWatch.h>
@interface TGBridgeStickerPack : NSObject <NSCoding>
{
bool _builtIn;
NSString *_title;
NSArray *_documents;
}
@property (nonatomic, readonly, getter=isBuiltIn) bool builtIn;
@property (nonatomic, readonly) NSString *title;
@property (nonatomic, readonly) NSArray *documents;
@end
@@ -0,0 +1,30 @@
#import "TGBridgeStickerPack.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
NSString *const TGBridgeStickerPackBuiltInKey = @"builtin";
NSString *const TGBridgeStickerPackTitleKey = @"title";
NSString *const TGBridgeStickerPackDocumentsKey = @"documents";
@implementation TGBridgeStickerPack
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self != nil)
{
_builtIn = [aDecoder decodeBoolForKey:TGBridgeStickerPackBuiltInKey];
_title = [aDecoder decodeObjectForKey:TGBridgeStickerPackTitleKey];
_documents = [aDecoder decodeObjectForKey:TGBridgeStickerPackDocumentsKey];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeBool:self.builtIn forKey:TGBridgeStickerPackBuiltInKey];
[aCoder encodeObject:self.title forKey:TGBridgeStickerPackTitleKey];
[aCoder encodeObject:self.documents forKey:TGBridgeStickerPackDocumentsKey];
}
@end
@@ -0,0 +1,10 @@
#import <SSignalKit/SSignalKit.h>
@interface TGBridgeStickersSignals : NSObject
+ (SSignal *)recentStickersWithLimit:(NSUInteger)limit;
+ (SSignal *)stickerPacks;
+ (NSURL *)stickerPacksURL;
@end
@@ -0,0 +1,50 @@
#import "TGBridgeStickersSignals.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeStickerPack.h"
#import "TGBridgeClient.h"
@implementation TGBridgeStickersSignals
static NSArray *cachedStickers = nil;
+ (SSignal *)cachedRecentStickers
{
return [SSignal single:cachedStickers];
}
+ (SSignal *)recentStickersWithLimit:(NSUInteger)limit
{
return [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeRecentStickersSubscription alloc] initWithLimit:limit]];
// return [[self cachedRecentStickers] mapToSignal:^SSignal *(NSArray *stickers) {
// SSignal *remote = [[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeRecentStickersSubscription alloc] initWithLimit:limit]];
// remote = [remote onNext:^(NSArray *stickers) {
// cachedStickers = stickers;
// }];
// if (stickers != nil) {
// return [[SSignal single:stickers] then:remote];
// } else {
// return remote;
// }
// }];
}
+ (SSignal *)stickerPacks
{
return [[SSignal single:[[TGBridgeClient instance] stickerPacks]] then:[[TGBridgeClient instance] fileSignalForKey:@"stickers"]];
}
+ (NSURL *)stickerPacksURL
{
static dispatch_once_t onceToken;
static NSURL *stickerPacksURL;
dispatch_once(&onceToken, ^
{
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true)[0];
stickerPacksURL = [[NSURL alloc] initFileURLWithPath:[documentsPath stringByAppendingPathComponent:@"stickers.data"]];
});
return stickerPacksURL;
}
@end
@@ -0,0 +1,7 @@
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "WKInterfaceTable+TGDataDrivenTable.h"
@interface TGBridgeUser (TGTableItem) <TGTableItem>
@end
@@ -0,0 +1,10 @@
#import "TGBridgeUser+TGTableItem.h"
@implementation TGBridgeUser (TGTableItem)
- (NSString *)uniqueIdentifier
{
return [NSString stringWithFormat:@"%d", self.identifier];
}
@end
@@ -0,0 +1,8 @@
#import <SSignalKit/SSignalKit.h>
@interface TGBridgeUserInfoSignals : NSObject
+ (SSignal *)userInfoWithUserId:(int32_t)userId;
+ (SSignal *)usersInfoWithUserIds:(NSArray *)userIds;
@end
@@ -0,0 +1,25 @@
#import "TGBridgeUserInfoSignals.h"
#import <WatchCommonWatch/WatchCommonWatch.h>
#import "TGBridgeClient.h"
@implementation TGBridgeUserInfoSignals
+ (SSignal *)userInfoWithUserId:(int32_t)userId;
{
return [[self usersInfoWithUserIds:@[ @(userId) ]] map:^TGBridgeUser *(NSDictionary *users)
{
return users[@(userId)];
}];
}
+ (SSignal *)usersInfoWithUserIds:(NSArray *)userIds
{
return [[[TGBridgeClient instance] requestSignalWithSubscription:[[TGBridgeUserInfoSubscription alloc] initWithUserIds:userIds]] map:^NSDictionary *(id next)
{
return next;
}];
}
@end