Skip to content
This repository has been archived by the owner on Dec 18, 2022. It is now read-only.

Commit

Permalink
Avatars
Browse files Browse the repository at this point in the history
- Created SKAvatar to manage downloaded avatars
- Can now download the avatar in any size
- Downloading snapcodez works now
  • Loading branch information
NSExceptional committed Jan 2, 2016
1 parent e3c65a8 commit 19cdecc
Show file tree
Hide file tree
Showing 11 changed files with 1,481 additions and 1,134 deletions.
4 changes: 2 additions & 2 deletions Example/Pods/Local Podspecs/SnapchatKit.podspec.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2,234 changes: 1,125 additions & 1,109 deletions Example/Pods/Pods.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions Example/SnapchatKit-OSX/SnapchatKit-OSX/main.m
Expand Up @@ -244,6 +244,21 @@ int main(int argc, const char * argv[]) {

/// Notes ///

// [[SKClient sharedClient] downloadAvatar:@"tannerbennett" completion:^(SKAvatar *avatar, NSError *error) {
// if (!error) {
// NSMutableArray *images = [NSMutableArray array];
// for (NSData *data in avatar.frames)
// [images addObject:[[NSImage alloc] initWithData:data]];
//
// NSData *reversed = [SKAvatar avatarDataFromImageDatas:avatar.frames];
// SKAvatar *backAgain = [SKAvatar avatarWithData:reversed error:nil];
// reversed = [reversed subdataWithRange:NSMakeRange(9, reversed.length-9)]; // ignore timestamp and count
// NSData *original = [avatar.data subdataWithRange:NSMakeRange(9, avatar.data.length-9)];
// BOOL success = backAgain && [reversed isEqualToData:original];
// success = success;
// }
// }];


// Get unread snaps
NSArray *unread = session.unread;
Expand Down
36 changes: 36 additions & 0 deletions Pod/Classes/Model/SKAvatar.h
@@ -0,0 +1,36 @@
//
// SKAvatar.h
// Pods
//
// Created by Tanner on 1/2/16.
//
//

#import <Foundation/Foundation.h>
#define USE_UIKIT (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR)

NS_ASSUME_NONNULL_BEGIN
@interface SKAvatar : NSObject

/** @return \c nil if an avatar could not be created from the given data. */
+ (nullable instancetype)avatarWithData:(NSData *)data error:(NSError * _Nullable *)error;
+ (NSData *)avatarDataFromImageDatas:(NSArray<NSData*> *)imageDatas;

@property (nonatomic, readonly) NSString *username;
@property (nonatomic, readonly) NSDate *lastUpdated;
@property (nonatomic, readonly) NSData *data;

#if USE_UIKIT
/** An animated image representing the avatar. */
@property (nonatomic, readonly) UIImage *image;
#else
/** Use as you wish, \c nil by default. See \c setImage:freeFrames: */
@property (nullable, nonatomic, readonly) NSImage *image;
@property (nullable, nonatomic, readonly) NSArray<NSData*> *frames;
/** @param image The image to set to the \c image property.
@param freeFrames Whether or not to release the \c frames property. */
- (void)setImage:(NSImage *)image freeFrames:(BOOL)freeFrames;
#endif

@end
NS_ASSUME_NONNULL_END
196 changes: 196 additions & 0 deletions Pod/Classes/Model/SKAvatar.m
@@ -0,0 +1,196 @@
//
// SKAvatar.m
// Pods
//
// Created by Tanner on 1/2/16.
//
//

#import "SKAvatar.h"
#import <zlib.h>

#if USE_UIKIT
@import UIKit;
#else
@import AppKit;
#endif


@implementation SKAvatar

+ (instancetype)avatarWithData:(NSData *)data error:(NSError **)error {
SKAvatar *avatar = [SKAvatar new];
avatar->_username = [self usernameFromData:&data];
NSArray *datas = [self imageDataFromAvatarData:data error:error];

// Return nil if error
if (!datas.count)
return nil;

#if USE_UIKIT
NSMutableArray *images = [NSMutableArray array];
for (NSData *data in datas)
[images addObject:[UIImage imageWithData:data]];
avatar->_image = [UIImage animatedImageWithImages:images duration:1];
#else
avatar->_frames = datas.copy;
#endif

avatar->_lastUpdated = [self lastUpdatedFromAvatarData:data];
avatar->_data = data;

return avatar;
}

#if !USE_UIKIT
- (void)setImage:(NSImage *)image freeFrames:(BOOL)freeFrames {
NSParameterAssert(image);

_image = image;
if (freeFrames)
_frames = nil;
}
#endif

#pragma mark - Internal

+ (NSError *)errorWithMessage:(NSString *)message code:(NSInteger)code {
return [NSError errorWithDomain:@"SnapchatKit" code:code userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(message, @""),
NSLocalizedFailureReasonErrorKey: NSLocalizedString(message, @"")}];
}

+ (NSDate *)lastUpdatedFromAvatarData:(NSData *)data {
unsigned long lastUpdated = 0;
[data getBytes:&lastUpdated range:NSMakeRange(1, 8)];
lastUpdated = NSSwapLong(lastUpdated); // Big endian

return [NSDate dateWithTimeIntervalSince1970:lastUpdated/1000];
}

+ (NSArray<NSData*> *)imageDataFromAvatarData:(NSData *)data error:(NSError **)error {
NSParameterAssert(data);

// Get # images, must be 5
NSInteger count = 0;
[data getBytes:&count length:1];
if (count != 5) {
// Scan for leading username, will be terminated by \0\0
// char c = '\0';
// NSInteger len = 0;
// do {
// [data getBytes:&c range:NSMakeRange(len++, 1)];
// } while (isalnum(c));
//
// // Try again after finding the username
// if (len > 1 && c == '\0') {
// len += 1; // Here len is 1 more than the username but we need it 2 more than the username
// data = [data subdataWithRange:NSMakeRange(len, data.length - len)];
// return [self imageDataFromAvatarData:data error:error];
// } else {
NSString *message = [NSString stringWithFormat:@"Count of %@ images does not match expected count of 5", @(count)];
if (error) *error = [self errorWithMessage:message code:1];
return nil;
// }
}

// Get data
NSMutableArray *datas = [NSMutableArray array];
NSInteger offset = 9;
for (NSInteger i = 0; i < 5; i++) {
unsigned int size = 0; unsigned long hash = 0;

// Read size
[data getBytes:&size range:NSMakeRange(offset, 4)];
size = NSSwapInt(size); // Big endian
offset += 4;

// Read image
[datas addObject:[data subdataWithRange:NSMakeRange(offset, size)]];
offset += size;

// Read hash, might use later
[data getBytes:&hash range:NSMakeRange(offset, 8)];
offset += 8;
}

return datas;
}

+ (NSData *)avatarDataFromImageDatas:(NSArray<NSData*> *)imageDatas {
NSParameterAssert(imageDatas.count == 5);

NSMutableData *data = [NSMutableData dataWithCapacity:1 + 8 + 5 * (4 + 8)];

// Write count
short count = 5;
[data appendBytes:&count length:1];

// Write last updated ts
unsigned long updatedts = [[NSDate date] timeIntervalSince1970] * 1000;
updatedts = NSSwapLong(updatedts);
[data appendBytes:&updatedts length:8];

// Write sizes, images, hashes
for (NSData *image in imageDatas) {
unsigned int size = NSSwapInt( (unsigned int)image.length );
unsigned long hash = NSSwapLong( crc32(0, image.bytes, (unsigned int)image.length) );

[data appendBytes:&size length:4];
[data appendData:image];
[data appendBytes:&hash length:8];
}

return data;
}

+ (NSString *)usernameFromData:(NSData **)data {
char c = '\0';
NSInteger len = 0;
do {
[*data getBytes:&c range:NSMakeRange(len++, 1)];
} while (isalnum(c));


// Did we find a username?
if (len > 1 && c == '\0') {
len--;
NSString *username = [[NSString alloc] initWithData:[*data subdataWithRange:NSMakeRange(0, len)] encoding:NSUTF8StringEncoding];
len += 2; // we need it 2 more than the username to pass the \0\0 terminator
*data = [*data subdataWithRange:NSMakeRange(len, [(*data) length] - len)];
return username;
}

return nil;
}

@end






























23 changes: 17 additions & 6 deletions Pod/Classes/Networking/SKClient+Account.h
Expand Up @@ -54,18 +54,29 @@
- (void)updateFeatureSettings:(NSDictionary *)settings completion:(ErrorBlock)completion;

/** Downloads your account's snaptag, a personal Snapchat QR code.
@param completion Takes an error, if any, and an SKBlob object with the snaptag data. */
- (void)downloadSnaptag:(ResponseBlock)completion;
@param svg If \c YES, an SVG will be downloaded. If \c NO, a 320x320 PNG will be downloaded.
@param completion Takes an error, if any, and an \c NSData object with the snaptag PNG data, or a string with the SVG XML data. */
- (void)downloadSnaptagAsSVG:(BOOL)svg completion:(ResponseBlock)completion;
/** Downloads a user's snaptag, a personal Snapchat QR code.
@param svg If \c YES, an SVG will be downloaded. If \c NO, a 320x320 PNG will be downloaded.
@param completion Takes an error, if any, and an \c NSData object with the snaptag PNG data, or a string with the SVG XML data. */
- (void)downloadSnaptagForUser:(SKUser *)user asSVG:(BOOL)svg completion:(ResponseBlock)completion;

/** Uploads a new animated avatar. Not working yet.
@param datas An array of 5 image \c NSData objects.
@param completion Takes an error, if any. */
- (void)uploadAvatar:(NSArray *)datas completion:(ErrorBlock)completion;
/** Downloads the animated avatar for \c user. Currently encrypted, or something.
//- (void)uploadAvatar:(NSArray *)datas completion:(ErrorBlock)completion;

/** Downloads the animated avatar for \c user.
@param username The username tied to the avatar to download.
@param completion Takes an error, if any, and ideally an array of 5 \c SKBlob objects or \c nil if the user does not have an avatar, but as of now takes one possibly encrypted \c SKBlob object. */
- (void)downloadAvatar:(NSString *)username completion:(ResponseBlock)completion;
@param size The size of the image to download.
@param completion Takes an error, if any, and an \c SKAvatar object. */
- (void)downloadAvatar:(NSString *)username size:(SKAvatarSize)size completion:(ResponseBlock)completion;
/** Downloads your avatar. Completion takes an error, if any, and an \c SKAvatar object. */
- (void)downloadYourAvatar:(SKAvatarSize)size completion:(ResponseBlock)completion;
/** Removes your current avatar. */
- (void)removeYourAvatar:(ErrorBlock)completion;

/** Updates your TOS agreement status for each of the three Terms of Service's.
@param completion Takes an error, if any. */
- (void)updateTOSAgreementStatus:(BOOL)snapcash snapcashV2:(BOOL)snapcashV2 square:(BOOL)square completion:(ErrorBlock)completion;
Expand Down

0 comments on commit 19cdecc

Please sign in to comment.