mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-04 21:41:45 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
38ca457a26
2
.gitignore
vendored
2
.gitignore
vendored
@ -62,5 +62,5 @@ bazel-testlogs/*
|
||||
*/*.swp
|
||||
*.swp
|
||||
build-input/*
|
||||
*/*.pyc
|
||||
**/*.pyc
|
||||
*.pyc
|
||||
|
||||
@ -18,8 +18,8 @@ internal:
|
||||
except:
|
||||
- tags
|
||||
script:
|
||||
- bash buildbox/build-telegram.sh hockeyapp
|
||||
- bash buildbox/deploy-telegram.sh hockeyapp
|
||||
- PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="/telegram-private-data/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64
|
||||
- python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-internal.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
|
||||
environment:
|
||||
name: internal
|
||||
artifacts:
|
||||
@ -37,8 +37,8 @@ appstore_development:
|
||||
except:
|
||||
- tags
|
||||
script:
|
||||
- bash buildbox/build-telegram.sh appstore-development
|
||||
- bash buildbox/deploy-telegram.sh appstore-development
|
||||
- PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64
|
||||
- python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appstore-development.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
|
||||
environment:
|
||||
name: appstore-development
|
||||
artifacts:
|
||||
@ -48,15 +48,15 @@ appstore_development:
|
||||
|
||||
experimental_i:
|
||||
tags:
|
||||
- ios_internal
|
||||
- ios_experimental
|
||||
stage: build
|
||||
only:
|
||||
- experimental-3
|
||||
except:
|
||||
- tags
|
||||
script:
|
||||
- bash buildbox/build-telegram.sh appcenter-experimental
|
||||
- bash buildbox/deploy-telegram.sh appcenter-experimental
|
||||
- PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64
|
||||
- python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-experimental.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
|
||||
environment:
|
||||
name: experimental
|
||||
artifacts:
|
||||
@ -73,8 +73,8 @@ experimental:
|
||||
except:
|
||||
- tags
|
||||
script:
|
||||
- bash buildbox/build-telegram.sh appcenter-experimental-2
|
||||
- bash buildbox/deploy-telegram.sh appcenter-experimental-2
|
||||
- PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64
|
||||
- python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-experimental2.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
|
||||
environment:
|
||||
name: experimental-2
|
||||
artifacts:
|
||||
@ -92,7 +92,7 @@ beta_testflight:
|
||||
except:
|
||||
- tags
|
||||
script:
|
||||
- bash buildbox/build-telegram.sh appstore
|
||||
- PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appstore-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_universal
|
||||
environment:
|
||||
name: testflight_llc
|
||||
artifacts:
|
||||
|
||||
@ -464,7 +464,7 @@ private struct NotificationContent: CustomStringConvertible {
|
||||
return string
|
||||
}
|
||||
|
||||
mutating func addSenderInfo(mediaBox: MediaBox, accountPeerId: PeerId, peer: Peer) {
|
||||
mutating func addSenderInfo(mediaBox: MediaBox, accountPeerId: PeerId, peer: Peer, contactIdentifier: String?) {
|
||||
if #available(iOS 15.0, *) {
|
||||
let image = peerAvatar(mediaBox: mediaBox, accountPeerId: accountPeerId, peer: peer)
|
||||
|
||||
@ -483,7 +483,7 @@ private struct NotificationContent: CustomStringConvertible {
|
||||
nameComponents: personNameComponents,
|
||||
displayName: displayName,
|
||||
image: image,
|
||||
contactIdentifier: nil,
|
||||
contactIdentifier: contactIdentifier,
|
||||
customIdentifier: "\(peer.id.toInt64())",
|
||||
isMe: false,
|
||||
suggestionType: .none
|
||||
@ -818,6 +818,7 @@ private final class NotificationServiceHandler {
|
||||
var updates: String
|
||||
var accountId: Int64
|
||||
var peer: EnginePeer?
|
||||
var localContactId: String?
|
||||
}
|
||||
|
||||
var callData: CallData?
|
||||
@ -1033,35 +1034,51 @@ private final class NotificationServiceHandler {
|
||||
if let action = action {
|
||||
switch action {
|
||||
case let .call(callData):
|
||||
let voipPayload: [AnyHashable: Any] = [
|
||||
"call_id": "\(callData.id)",
|
||||
"call_ah": "\(callData.accessHash)",
|
||||
"from_id": "\(callData.fromId.id._internalGetInt64Value())",
|
||||
"updates": callData.updates,
|
||||
"accountId": "\(callData.accountId)"
|
||||
]
|
||||
|
||||
if #available(iOS 14.5, *), voiceCallSettings.enableSystemIntegration {
|
||||
Logger.shared.log("NotificationService \(episode)", "Will report voip notification")
|
||||
if let stateManager = strongSelf.stateManager {
|
||||
let content = NotificationContent(isLockedMessage: nil)
|
||||
updateCurrentContent(content)
|
||||
|
||||
CXProvider.reportNewIncomingVoIPPushPayload(voipPayload, completion: { error in
|
||||
Logger.shared.log("NotificationService \(episode)", "Did report voip notification, error: \(String(describing: error))")
|
||||
let _ = (stateManager.postbox.transaction { transaction -> String? in
|
||||
if let peer = transaction.getPeer(callData.fromId) as? TelegramUser {
|
||||
return peer.phone
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}).start(next: { phoneNumber in
|
||||
var voipPayload: [AnyHashable: Any] = [
|
||||
"call_id": "\(callData.id)",
|
||||
"call_ah": "\(callData.accessHash)",
|
||||
"from_id": "\(callData.fromId.id._internalGetInt64Value())",
|
||||
"updates": callData.updates,
|
||||
"accountId": "\(callData.accountId)"
|
||||
]
|
||||
if let phoneNumber = phoneNumber {
|
||||
voipPayload["phoneNumber"] = phoneNumber
|
||||
}
|
||||
|
||||
completed()
|
||||
if #available(iOS 14.5, *), voiceCallSettings.enableSystemIntegration {
|
||||
Logger.shared.log("NotificationService \(episode)", "Will report voip notification")
|
||||
let content = NotificationContent(isLockedMessage: nil)
|
||||
updateCurrentContent(content)
|
||||
|
||||
CXProvider.reportNewIncomingVoIPPushPayload(voipPayload, completion: { error in
|
||||
Logger.shared.log("NotificationService \(episode)", "Did report voip notification, error: \(String(describing: error))")
|
||||
|
||||
completed()
|
||||
})
|
||||
} else {
|
||||
var content = NotificationContent(isLockedMessage: nil)
|
||||
if let peer = callData.peer {
|
||||
content.title = peer.debugDisplayTitle
|
||||
content.body = incomingCallMessage
|
||||
} else {
|
||||
content.body = "Incoming Call"
|
||||
}
|
||||
|
||||
updateCurrentContent(content)
|
||||
completed()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
var content = NotificationContent(isLockedMessage: nil)
|
||||
if let peer = callData.peer {
|
||||
content.title = peer.debugDisplayTitle
|
||||
content.body = incomingCallMessage
|
||||
} else {
|
||||
content.body = "Incoming Call"
|
||||
}
|
||||
|
||||
updateCurrentContent(content)
|
||||
completed()
|
||||
}
|
||||
case .logout:
|
||||
Logger.shared.log("NotificationService \(episode)", "Will logout")
|
||||
@ -1334,7 +1351,23 @@ private final class NotificationServiceHandler {
|
||||
|
||||
if let interactionAuthorId = interactionAuthorId {
|
||||
if inAppNotificationSettings.displayNameOnLockscreen, let peer = transaction.getPeer(interactionAuthorId) {
|
||||
content.addSenderInfo(mediaBox: stateManager.postbox.mediaBox, accountPeerId: stateManager.accountPeerId, peer: peer)
|
||||
var foundLocalId: String?
|
||||
transaction.enumerateDeviceContactImportInfoItems({ _, value in
|
||||
if let value = value as? TelegramDeviceContactImportedData {
|
||||
switch value {
|
||||
case let .imported(data, _, peerId):
|
||||
if peerId == interactionAuthorId {
|
||||
foundLocalId = data.localIdentifiers.first
|
||||
return false
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
content.addSenderInfo(mediaBox: stateManager.postbox.mediaBox, accountPeerId: stateManager.accountPeerId, peer: peer, contactIdentifier: foundLocalId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
311
build-system/Make/BuildConfiguration.py
Normal file
311
build-system/Make/BuildConfiguration.py
Normal file
@ -0,0 +1,311 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import tempfile
|
||||
import plistlib
|
||||
|
||||
from BuildEnvironment import run_executable_with_output, check_run_system
|
||||
|
||||
class BuildConfiguration:
|
||||
def __init__(self,
|
||||
bundle_id,
|
||||
api_id,
|
||||
api_hash,
|
||||
team_id,
|
||||
app_center_id,
|
||||
is_internal_build,
|
||||
is_appstore_build,
|
||||
appstore_id,
|
||||
app_specific_url_scheme,
|
||||
premium_iap_product_id,
|
||||
enable_siri,
|
||||
enable_icloud
|
||||
):
|
||||
self.bundle_id = bundle_id
|
||||
self.api_id = api_id
|
||||
self.api_hash = api_hash
|
||||
self.team_id = team_id
|
||||
self.app_center_id = app_center_id
|
||||
self.is_internal_build = is_internal_build
|
||||
self.is_appstore_build = is_appstore_build
|
||||
self.appstore_id = appstore_id
|
||||
self.app_specific_url_scheme = app_specific_url_scheme
|
||||
self.premium_iap_product_id = premium_iap_product_id
|
||||
self.enable_siri = enable_siri
|
||||
self.enable_icloud = enable_icloud
|
||||
|
||||
def write_to_variables_file(self, aps_environment, path):
|
||||
string = ''
|
||||
string += 'telegram_bundle_id = "{}"\n'.format(self.bundle_id)
|
||||
string += 'telegram_api_id = "{}"\n'.format(self.api_id)
|
||||
string += 'telegram_api_hash = "{}"\n'.format(self.api_hash)
|
||||
string += 'telegram_team_id = "{}"\n'.format(self.team_id)
|
||||
string += 'telegram_app_center_id = "{}"\n'.format(self.app_center_id)
|
||||
string += 'telegram_is_internal_build = "{}"\n'.format(self.is_internal_build)
|
||||
string += 'telegram_is_appstore_build = "{}"\n'.format(self.is_appstore_build)
|
||||
string += 'telegram_appstore_id = "{}"\n'.format(self.appstore_id)
|
||||
string += 'telegram_app_specific_url_scheme = "{}"\n'.format(self.app_specific_url_scheme)
|
||||
string += 'telegram_premium_iap_product_id = "{}"\n'.format(self.premium_iap_product_id)
|
||||
string += 'telegram_aps_environment = "{}"\n'.format(aps_environment)
|
||||
string += 'telegram_enable_siri = {}\n'.format(self.enable_siri)
|
||||
string += 'telegram_enable_icloud = {}\n'.format(self.enable_icloud)
|
||||
string += 'telegram_enable_watch = True\n'
|
||||
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
with open(path, 'w+') as file:
|
||||
file.write(string)
|
||||
|
||||
|
||||
def build_configuration_from_json(path):
|
||||
if not os.path.exists(path):
|
||||
print('Could not load build configuration from {}'.format(path))
|
||||
sys.exit(1)
|
||||
with open(path) as file:
|
||||
configuration_dict = json.load(file)
|
||||
required_keys = [
|
||||
'bundle_id',
|
||||
'api_id',
|
||||
'api_hash',
|
||||
'team_id',
|
||||
'app_center_id',
|
||||
'is_internal_build',
|
||||
'is_appstore_build',
|
||||
'appstore_id',
|
||||
'app_specific_url_scheme',
|
||||
'premium_iap_product_id',
|
||||
'enable_siri',
|
||||
'enable_icloud'
|
||||
]
|
||||
for key in required_keys:
|
||||
if key not in configuration_dict:
|
||||
print('Configuration at {} does not contain {}'.format(path, key))
|
||||
return BuildConfiguration(
|
||||
bundle_id=configuration_dict['bundle_id'],
|
||||
api_id=configuration_dict['api_id'],
|
||||
api_hash=configuration_dict['api_hash'],
|
||||
team_id=configuration_dict['team_id'],
|
||||
app_center_id=configuration_dict['app_center_id'],
|
||||
is_internal_build=configuration_dict['is_internal_build'],
|
||||
is_appstore_build=configuration_dict['is_appstore_build'],
|
||||
appstore_id=configuration_dict['appstore_id'],
|
||||
app_specific_url_scheme=configuration_dict['app_specific_url_scheme'],
|
||||
premium_iap_product_id=configuration_dict['premium_iap_product_id'],
|
||||
enable_siri=configuration_dict['enable_siri'],
|
||||
enable_icloud=configuration_dict['enable_icloud']
|
||||
)
|
||||
|
||||
|
||||
def decrypt_codesigning_directory_recursively(source_base_path, destination_base_path, password):
|
||||
for file_name in os.listdir(source_base_path):
|
||||
source_path = source_base_path + '/' + file_name
|
||||
destination_path = destination_base_path + '/' + file_name
|
||||
if os.path.isfile(source_path):
|
||||
os.system('openssl aes-256-cbc -md md5 -k "{password}" -in "{source_path}" -out "{destination_path}" -a -d 2>/dev/null'.format(
|
||||
password=password,
|
||||
source_path=source_path,
|
||||
destination_path=destination_path
|
||||
))
|
||||
elif os.path.isdir(source_path):
|
||||
os.makedirs(destination_path, exist_ok=True)
|
||||
decrypt_codesigning_directory_recursively(source_path, destination_path, password)
|
||||
|
||||
|
||||
def load_codesigning_data_from_git(working_dir, repo_url, temp_key_path, branch, password, always_fetch):
|
||||
if not os.path.exists(working_dir):
|
||||
os.makedirs(working_dir, exist_ok=True)
|
||||
|
||||
ssh_command = 'ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
|
||||
if temp_key_path is not None:
|
||||
ssh_command += ' -i {}'.format(temp_key_path)
|
||||
|
||||
encrypted_working_dir = working_dir + '/encrypted'
|
||||
if os.path.exists(encrypted_working_dir):
|
||||
if always_fetch:
|
||||
original_working_dir = os.getcwd()
|
||||
os.chdir(encrypted_working_dir)
|
||||
check_run_system('GIT_SSH_COMMAND="{ssh_command}" git fetch'.format(ssh_command=ssh_command))
|
||||
check_run_system('git checkout "{branch}"'.format(branch=branch))
|
||||
check_run_system('GIT_SSH_COMMAND="{ssh_command}" git pull'.format(ssh_command=ssh_command))
|
||||
os.chdir(original_working_dir)
|
||||
else:
|
||||
os.makedirs(encrypted_working_dir, exist_ok=True)
|
||||
original_working_dir = os.getcwd()
|
||||
os.chdir(working_dir)
|
||||
check_run_system('GIT_SSH_COMMAND="{ssh_command}" git clone --depth=1 {repo_url} -b "{branch}" "{target_path}"'.format(
|
||||
ssh_command=ssh_command,
|
||||
repo_url=repo_url,
|
||||
branch=branch,
|
||||
target_path=encrypted_working_dir
|
||||
))
|
||||
os.chdir(original_working_dir)
|
||||
|
||||
decrypted_working_dir = working_dir + '/decrypted'
|
||||
if os.path.exists(decrypted_working_dir):
|
||||
shutil.rmtree(decrypted_working_dir)
|
||||
os.makedirs(decrypted_working_dir, exist_ok=True)
|
||||
|
||||
decrypt_codesigning_directory_recursively(encrypted_working_dir + '/profiles', decrypted_working_dir + '/profiles', password)
|
||||
decrypt_codesigning_directory_recursively(encrypted_working_dir + '/certs', decrypted_working_dir + '/certs', password)
|
||||
|
||||
|
||||
def copy_profiles_from_directory(source_path, destination_path, team_id, bundle_id):
|
||||
profile_name_mapping = {
|
||||
'.SiriIntents': 'Intents',
|
||||
'.NotificationContent': 'NotificationContent',
|
||||
'.NotificationService': 'NotificationService',
|
||||
'.Share': 'Share',
|
||||
'': 'Telegram',
|
||||
'.watchkitapp': 'WatchApp',
|
||||
'.watchkitapp.watchkitextension': 'WatchExtension',
|
||||
'.Widget': 'Widget',
|
||||
'.BroadcastUpload': 'BroadcastUpload'
|
||||
}
|
||||
|
||||
for file_name in os.listdir(source_path):
|
||||
file_path = source_path + '/' + file_name
|
||||
if os.path.isfile(file_path):
|
||||
if not file_path.endswith('.mobileprovision'):
|
||||
continue
|
||||
|
||||
profile_data = run_executable_with_output('openssl', arguments=[
|
||||
'smime',
|
||||
'-inform',
|
||||
'der',
|
||||
'-verify',
|
||||
'-noverify',
|
||||
'-in',
|
||||
file_path
|
||||
], decode=False, stderr_to_stdout=False, check_result=True)
|
||||
|
||||
profile_dict = plistlib.loads(profile_data)
|
||||
profile_name = profile_dict['Entitlements']['application-identifier']
|
||||
|
||||
if profile_name.startswith(team_id + '.' + bundle_id):
|
||||
profile_base_name = profile_name[len(team_id + '.' + bundle_id):]
|
||||
if profile_base_name in profile_name_mapping:
|
||||
shutil.copyfile(file_path, destination_path + '/' + profile_name_mapping[profile_base_name] + '.mobileprovision')
|
||||
else:
|
||||
print('Warning: skipping provisioning profile at {} with bundle_id {} (base_name {})'.format(file_path, profile_name, profile_base_name))
|
||||
|
||||
|
||||
def resolve_aps_environment_from_directory(source_path, team_id, bundle_id):
|
||||
for file_name in os.listdir(source_path):
|
||||
file_path = source_path + '/' + file_name
|
||||
if os.path.isfile(file_path):
|
||||
if not file_path.endswith('.mobileprovision'):
|
||||
continue
|
||||
|
||||
profile_data = run_executable_with_output('openssl', arguments=[
|
||||
'smime',
|
||||
'-inform',
|
||||
'der',
|
||||
'-verify',
|
||||
'-noverify',
|
||||
'-in',
|
||||
file_path
|
||||
], decode=False, stderr_to_stdout=False, check_result=True)
|
||||
|
||||
profile_dict = plistlib.loads(profile_data)
|
||||
profile_name = profile_dict['Entitlements']['application-identifier']
|
||||
|
||||
if profile_name.startswith(team_id + '.' + bundle_id):
|
||||
profile_base_name = profile_name[len(team_id + '.' + bundle_id):]
|
||||
if profile_base_name == '':
|
||||
if 'aps-environment' not in profile_dict['Entitlements']:
|
||||
print('Provisioning profile at {} does not include an aps-environment entitlement'.format(file_path))
|
||||
sys.exit(1)
|
||||
return profile_dict['Entitlements']['aps-environment']
|
||||
return None
|
||||
|
||||
|
||||
def copy_certificates_from_directory(source_path, destination_path):
|
||||
for file_name in os.listdir(source_path):
|
||||
file_path = source_path + '/' + file_name
|
||||
if os.path.isfile(file_path):
|
||||
if file_path.endswith('.p12') or file_path.endswith('.cer'):
|
||||
shutil.copyfile(file_path, destination_path + '/' + file_name)
|
||||
|
||||
|
||||
class CodesigningSource:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def load_data(self, working_dir):
|
||||
raise Exception('Not implemented')
|
||||
|
||||
def copy_profiles_to_destination(self, destination_path):
|
||||
raise Exception('Not implemented')
|
||||
|
||||
def resolve_aps_environment(self):
|
||||
raise Exception('Not implemented')
|
||||
|
||||
def copy_certificates_to_destination(self, destination_path):
|
||||
raise Exception('Not implemented')
|
||||
|
||||
|
||||
class GitCodesigningSource(CodesigningSource):
|
||||
def __init__(self, repo_url, private_key, team_id, bundle_id, codesigning_type, password, always_fetch):
|
||||
self.repo_url = repo_url
|
||||
self.private_key = private_key
|
||||
self.team_id = team_id
|
||||
self.bundle_id = bundle_id
|
||||
self.codesigning_type = codesigning_type
|
||||
self.password = password
|
||||
self.always_fetch = always_fetch
|
||||
|
||||
def load_data(self, working_dir):
|
||||
self.working_dir = working_dir
|
||||
temp_key_path = None
|
||||
if self.private_key is not None:
|
||||
temp_key_path = tempfile.mktemp()
|
||||
with open(temp_key_path, 'w+') as file:
|
||||
file.write(self.private_key)
|
||||
if not self.private_key.endswith('\n'):
|
||||
file.write('\n')
|
||||
os.chmod(temp_key_path, 0o600)
|
||||
|
||||
load_codesigning_data_from_git(working_dir=self.working_dir, repo_url=self.repo_url, temp_key_path=temp_key_path, branch=self.team_id, password=self.password, always_fetch=self.always_fetch)
|
||||
|
||||
if temp_key_path is not None:
|
||||
os.remove(temp_key_path)
|
||||
|
||||
def copy_profiles_to_destination(self, destination_path):
|
||||
source_path = self.working_dir + '/decrypted/profiles/{}'.format(self.codesigning_type)
|
||||
copy_profiles_from_directory(source_path=source_path, destination_path=destination_path, team_id=self.team_id, bundle_id=self.bundle_id)
|
||||
|
||||
def resolve_aps_environment(self):
|
||||
source_path = self.working_dir + '/decrypted/profiles/{}'.format(self.codesigning_type)
|
||||
return resolve_aps_environment_from_directory(source_path=source_path, team_id=self.team_id, bundle_id=self.bundle_id)
|
||||
|
||||
def copy_certificates_to_destination(self, destination_path):
|
||||
source_path = None
|
||||
if self.codesigning_type in ['adhoc', 'appstore']:
|
||||
source_path = self.working_dir + '/decrypted/certs/distribution'
|
||||
elif self.codesigning_type == 'enterprise':
|
||||
source_path = self.working_dir + '/decrypted/certs/enterprise'
|
||||
elif self.codesigning_type == 'development':
|
||||
source_path = self.working_dir + '/decrypted/certs/development'
|
||||
else:
|
||||
raise Exception('Unknown codesigning type {}'.format(self.codesigning_type))
|
||||
copy_certificates_from_directory(source_path=source_path, destination_path=destination_path)
|
||||
|
||||
|
||||
class DirectoryCodesigningSource(CodesigningSource):
|
||||
def __init__(self, directory_path, team_id, bundle_id):
|
||||
self.directory_path = directory_path
|
||||
self.team_id = team_id
|
||||
self.bundle_id = bundle_id
|
||||
|
||||
def load_data(self, working_dir):
|
||||
pass
|
||||
|
||||
def copy_profiles_to_destination(self, destination_path):
|
||||
copy_profiles_from_directory(source_path=self.directory_path + '/profiles', destination_path=destination_path, team_id=self.team_id, bundle_id=self.bundle_id)
|
||||
|
||||
def resolve_aps_environment(self):
|
||||
return resolve_aps_environment_from_directory(source_path=self.directory_path + '/profiles', team_id=self.team_id, bundle_id=self.bundle_id)
|
||||
|
||||
def copy_certificates_to_destination(self, destination_path):
|
||||
copy_certificates_from_directory(source_path=self.directory_path + '/certs', destination_path=destination_path)
|
||||
@ -2,7 +2,7 @@ import json
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
import sys
|
||||
|
||||
def is_apple_silicon():
|
||||
if platform.processor() == 'arm':
|
||||
@ -28,20 +28,41 @@ def resolve_executable(program):
|
||||
return None
|
||||
|
||||
|
||||
def run_executable_with_output(path, arguments):
|
||||
def run_executable_with_output(path, arguments, decode=True, input=None, stderr_to_stdout=True, print_command=False, check_result=False):
|
||||
executable_path = resolve_executable(path)
|
||||
if executable_path is None:
|
||||
raise Exception('Could not resolve {} to a valid executable file'.format(path))
|
||||
|
||||
stderr_assignment = subprocess.DEVNULL
|
||||
if stderr_to_stdout:
|
||||
stderr_assignment = subprocess.STDOUT
|
||||
|
||||
if print_command:
|
||||
print('Running {} {}'.format(executable_path, arguments))
|
||||
|
||||
process = subprocess.Popen(
|
||||
[executable_path] + arguments,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
stderr=stderr_assignment,
|
||||
stdin=subprocess.PIPE,
|
||||
env=get_clean_env()
|
||||
)
|
||||
output_data, _ = process.communicate()
|
||||
if input is not None:
|
||||
output_data, _ = process.communicate(input=input)
|
||||
else:
|
||||
output_data, _ = process.communicate()
|
||||
|
||||
output_string = output_data.decode('utf-8')
|
||||
return output_string
|
||||
|
||||
if check_result:
|
||||
if process.returncode != 0:
|
||||
print('Command {} {} finished with non-zero return code and output:\n{}'.format(executable_path, arguments, output_string))
|
||||
sys.exit(1)
|
||||
|
||||
if decode:
|
||||
return output_string
|
||||
else:
|
||||
return output_data
|
||||
|
||||
|
||||
def call_executable(arguments, use_clean_environment=True, check_result=True):
|
||||
@ -62,6 +83,12 @@ def call_executable(arguments, use_clean_environment=True, check_result=True):
|
||||
subprocess.call(resolved_arguments, env=resolved_env)
|
||||
|
||||
|
||||
def check_run_system(command):
|
||||
if os.system(command) != 0:
|
||||
print('Command failed: {}'.format(command))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_bazel_version(bazel_path):
|
||||
command_result = run_executable_with_output(bazel_path, ['--version']).strip('\n')
|
||||
if not command_result.startswith('bazel '):
|
||||
|
||||
70
build-system/Make/DeployToAppCenter.py
Normal file
70
build-system/Make/DeployToAppCenter.py
Normal file
@ -0,0 +1,70 @@
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from BuildEnvironment import check_run_system
|
||||
|
||||
def deploy_to_appcenter(args):
|
||||
if not os.path.exists(args.configuration):
|
||||
print('{} does not exist'.format(args.configuration))
|
||||
sys.exit(1)
|
||||
if not os.path.exists(args.ipa):
|
||||
print('{} does not exist'.format(args.ipa))
|
||||
sys.exit(1)
|
||||
if args.dsyms is not None and not os.path.exists(args.dsyms):
|
||||
print('{} does not exist'.format(args.dsyms))
|
||||
sys.exit(1)
|
||||
|
||||
with open(args.configuration) as file:
|
||||
configuration_dict = json.load(file)
|
||||
required_keys = [
|
||||
'username',
|
||||
'app_name',
|
||||
'api_token',
|
||||
]
|
||||
for key in required_keys:
|
||||
if key not in configuration_dict:
|
||||
print('Configuration at {} does not contain {}'.format(args.configuration, key))
|
||||
|
||||
check_run_system('appcenter login --token {token}'.format(token=configuration_dict['api_token']))
|
||||
check_run_system('appcenter distribute release --app "{username}/{app_name}" -f "{ipa_path}" -g Internal'.format(
|
||||
username=configuration_dict['username'],
|
||||
app_name=configuration_dict['app_name'],
|
||||
ipa_path=args.ipa,
|
||||
|
||||
))
|
||||
if args.dsyms is not None:
|
||||
check_run_system('appcenter crashes upload-symbols --app "{username}/{app_name}" --symbol "{dsym_path}"'.format(
|
||||
username=configuration_dict['username'],
|
||||
app_name=configuration_dict['app_name'],
|
||||
dsym_path=args.dsyms
|
||||
))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(prog='deploy-appcenter')
|
||||
|
||||
parser.add_argument(
|
||||
'--configuration',
|
||||
required=True,
|
||||
help='Path to configuration json.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--ipa',
|
||||
required=True,
|
||||
help='Path to IPA.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dsyms',
|
||||
required=False,
|
||||
help='Path to DSYMs.zip.'
|
||||
)
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
deploy_to_appcenter(args)
|
||||
62
build-system/Make/DeployToAppStoreConnect.py
Normal file
62
build-system/Make/DeployToAppStoreConnect.py
Normal file
@ -0,0 +1,62 @@
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from BuildEnvironment import check_run_system
|
||||
|
||||
def deploy_to_appstore_connect(args):
|
||||
if not os.path.exists(args.configuration):
|
||||
print('{} does not exist'.format(args.configuration))
|
||||
sys.exit(1)
|
||||
if not os.path.exists(args.ipa):
|
||||
print('{} does not exist'.format(args.ipa))
|
||||
sys.exit(1)
|
||||
|
||||
with open(args.configuration) as file:
|
||||
configuration_dict = json.load(file)
|
||||
required_keys = [
|
||||
'username',
|
||||
'app_name',
|
||||
'api_token',
|
||||
]
|
||||
for key in required_keys:
|
||||
if key not in configuration_dict:
|
||||
print('Configuration at {} does not contain {}'.format(args.configuration, key))
|
||||
|
||||
check_run_system('appcenter login --token {token}'.format(token=configuration_dict['api_token']))
|
||||
check_run_system('appcenter distribute release --app "{username}/{app_name}" -f "{ipa_path}" -g Internal'.format(
|
||||
username=configuration_dict['username'],
|
||||
app_name=configuration_dict['app_name'],
|
||||
ipa_path=args.ipa,
|
||||
|
||||
))
|
||||
if args.dsyms is not None:
|
||||
check_run_system('appcenter crashes upload-symbols --app "{username}/{app_name}" --symbol "{dsym_path}"'.format(
|
||||
username=configuration_dict['username'],
|
||||
app_name=configuration_dict['app_name'],
|
||||
dsym_path=args.dsyms
|
||||
))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(prog='deploy-appstore-connect')
|
||||
|
||||
parser.add_argument(
|
||||
'--configuration',
|
||||
required=True,
|
||||
help='Path to configuration json.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--ipa',
|
||||
required=True,
|
||||
help='Path to IPA.'
|
||||
)
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
deploy_to_appstore_connect(args)
|
||||
95
build-system/Make/ImportCertificates.py
Normal file
95
build-system/Make/ImportCertificates.py
Normal file
@ -0,0 +1,95 @@
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
from BuildEnvironment import run_executable_with_output
|
||||
|
||||
def import_certificates(certificatesPath):
|
||||
if not os.path.exists(certificatesPath):
|
||||
print('{} does not exist'.format(certificatesPath))
|
||||
sys.exit(1)
|
||||
|
||||
keychain_name = 'temp.keychain'
|
||||
keychain_password = 'secret'
|
||||
|
||||
existing_keychains = run_executable_with_output('security', arguments=['list-keychains'], check_result=True)
|
||||
if keychain_name in existing_keychains:
|
||||
run_executable_with_output('security', arguments=['delete-keychain'], check_result=True)
|
||||
|
||||
run_executable_with_output('security', arguments=[
|
||||
'create-keychain',
|
||||
'-p',
|
||||
keychain_password,
|
||||
keychain_name
|
||||
], check_result=True)
|
||||
|
||||
existing_keychains = run_executable_with_output('security', arguments=['list-keychains', '-d', 'user'])
|
||||
existing_keychains.replace('"', '')
|
||||
|
||||
run_executable_with_output('security', arguments=[
|
||||
'list-keychains',
|
||||
'-d',
|
||||
'user',
|
||||
'-s',
|
||||
keychain_name,
|
||||
existing_keychains
|
||||
], check_result=True)
|
||||
|
||||
run_executable_with_output('security', arguments=['set-keychain-settings', keychain_name])
|
||||
run_executable_with_output('security', arguments=['unlock-keychain', '-p', keychain_password, keychain_name])
|
||||
|
||||
for file_name in os.listdir(certificatesPath):
|
||||
file_path = certificatesPath + '/' + file_name
|
||||
if file_path.endswith('.p12') or file_path.endswith('.cer'):
|
||||
run_executable_with_output('security', arguments=[
|
||||
'import',
|
||||
file_path,
|
||||
'-k',
|
||||
keychain_name,
|
||||
'-P',
|
||||
'',
|
||||
'-T',
|
||||
'/usr/bin/codesign',
|
||||
'-T',
|
||||
'/usr/bin/security'
|
||||
], check_result=True)
|
||||
|
||||
run_executable_with_output('security', arguments=[
|
||||
'import',
|
||||
'build-system/AppleWWDRCAG3.cer',
|
||||
'-k',
|
||||
keychain_name,
|
||||
'-P',
|
||||
'',
|
||||
'-T',
|
||||
'/usr/bin/codesign',
|
||||
'-T',
|
||||
'/usr/bin/security'
|
||||
], check_result=True)
|
||||
|
||||
run_executable_with_output('security', arguments=[
|
||||
'set-key-partition-list',
|
||||
'-S',
|
||||
'apple-tool:,apple:',
|
||||
'-k',
|
||||
keychain_password,
|
||||
keychain_name
|
||||
], check_result=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(prog='build')
|
||||
|
||||
parser.add_argument(
|
||||
'--path',
|
||||
required=True,
|
||||
help='Path to certificates.'
|
||||
)
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
import_certificates(args.path)
|
||||
@ -6,10 +6,20 @@ import shlex
|
||||
import sys
|
||||
import tempfile
|
||||
import subprocess
|
||||
import shutil
|
||||
import glob
|
||||
|
||||
from BuildEnvironment import resolve_executable, call_executable, BuildEnvironment
|
||||
from BuildEnvironment import resolve_executable, call_executable, run_executable_with_output, BuildEnvironment
|
||||
from ProjectGeneration import generate
|
||||
from BazelLocation import locate_bazel
|
||||
from BuildConfiguration import CodesigningSource, GitCodesigningSource, DirectoryCodesigningSource, BuildConfiguration, build_configuration_from_json
|
||||
import RemoteBuild
|
||||
|
||||
|
||||
class ResolvedCodesigningData:
|
||||
def __init__(self, aps_environment):
|
||||
self.aps_environment = aps_environment
|
||||
|
||||
|
||||
class BazelCommandLine:
|
||||
def __init__(self, bazel, override_bazel_version, override_xcode_version, bazel_user_root):
|
||||
@ -380,34 +390,95 @@ def clean(bazel, arguments):
|
||||
bazel_command_line.invoke_clean()
|
||||
|
||||
|
||||
def resolve_configuration(bazel_command_line: BazelCommandLine, arguments):
|
||||
if arguments.configurationGenerator is not None:
|
||||
configuration_generator_arguments = shlex.split(arguments.configurationGenerator)
|
||||
def resolve_codesigning(arguments, base_path, build_configuration, provisioning_profiles_path, additional_codesigning_output_path) -> ResolvedCodesigningData:
|
||||
profile_source = None
|
||||
if arguments.gitCodesigningRepository is not None:
|
||||
password = os.getenv('TELEGRAM_CODESIGNING_GIT_PASSWORD')
|
||||
if password is None:
|
||||
print('TELEGRAM_CODESIGNING_GIT_PASSWORD environment variable is not set')
|
||||
sys.exit(1)
|
||||
|
||||
configuration_generator_executable = resolve_executable(configuration_generator_arguments[0])
|
||||
if arguments.gitCodesigningType is None:
|
||||
print('--gitCodesigningType is required if --gitCodesigningRepository is set')
|
||||
sys.exit(1)
|
||||
|
||||
if configuration_generator_executable is None:
|
||||
print('{} is not a valid executable'.format(configuration_generator_arguments[0]))
|
||||
exit(1)
|
||||
private_key = os.getenv('TELEGRAM_CODESIGNING_PRIVATE_KEY')
|
||||
|
||||
temp_configuration_path = tempfile.mkdtemp()
|
||||
|
||||
resolved_configuration_generator_arguments = [configuration_generator_executable]
|
||||
resolved_configuration_generator_arguments += configuration_generator_arguments[1:]
|
||||
resolved_configuration_generator_arguments += [temp_configuration_path]
|
||||
|
||||
call_executable(resolved_configuration_generator_arguments, use_clean_environment=False)
|
||||
|
||||
print('TelegramBuild: using generated configuration in {}'.format(temp_configuration_path))
|
||||
bazel_command_line.set_configuration_path(temp_configuration_path)
|
||||
elif arguments.configurationPath is not None:
|
||||
absolute_configuration_path = os.path.abspath(arguments.configurationPath)
|
||||
if not os.path.isdir(absolute_configuration_path):
|
||||
print('Error: {} does not exist'.format(absolute_configuration_path))
|
||||
exit(1)
|
||||
bazel_command_line.set_configuration_path(absolute_configuration_path)
|
||||
profile_source = GitCodesigningSource(
|
||||
repo_url=arguments.gitCodesigningRepository,
|
||||
private_key=private_key,
|
||||
team_id=build_configuration.team_id,
|
||||
bundle_id=build_configuration.bundle_id,
|
||||
codesigning_type=arguments.gitCodesigningType,
|
||||
password=password,
|
||||
always_fetch=arguments.gitCodesigningAlwaysFetch
|
||||
)
|
||||
elif arguments.codesigningInformationPath is not None:
|
||||
profile_source = DirectoryCodesigningSource(
|
||||
directory_path=arguments.codesigningInformationPath,
|
||||
team_id=build_configuration.team_id,
|
||||
bundle_id=build_configuration.bundle_id
|
||||
)
|
||||
else:
|
||||
raise Exception('Neither configurationPath nor configurationGenerator are set')
|
||||
raise Exception('Neither gitCodesigningRepository nor codesigningInformationPath are set')
|
||||
|
||||
workdir_path = '{}/build-input/configuration-repository-workdir'.format(base_path)
|
||||
os.makedirs(workdir_path, exist_ok=True)
|
||||
profile_source.load_data(working_dir=workdir_path)
|
||||
|
||||
if provisioning_profiles_path is not None:
|
||||
profile_source.copy_profiles_to_destination(destination_path=provisioning_profiles_path)
|
||||
|
||||
if additional_codesigning_output_path is not None:
|
||||
profile_source.copy_profiles_to_destination(destination_path=additional_codesigning_output_path + '/profiles')
|
||||
profile_source.copy_certificates_to_destination(destination_path=additional_codesigning_output_path + '/certs')
|
||||
|
||||
return ResolvedCodesigningData(aps_environment=profile_source.resolve_aps_environment())
|
||||
|
||||
|
||||
def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, arguments, additional_codesigning_output_path):
|
||||
configuration_repository_path = '{}/build-input/configuration-repository'.format(base_path)
|
||||
os.makedirs(configuration_repository_path, exist_ok=True)
|
||||
|
||||
build_configuration = build_configuration_from_json(path=arguments.configurationPath)
|
||||
|
||||
with open(configuration_repository_path + '/WORKSPACE', 'w+') as file:
|
||||
pass
|
||||
|
||||
with open(configuration_repository_path + '/BUILD', 'w+') as file:
|
||||
pass
|
||||
|
||||
provisioning_path = '{}/provisioning'.format(configuration_repository_path)
|
||||
if os.path.exists(provisioning_path):
|
||||
shutil.rmtree(provisioning_path)
|
||||
os.makedirs(provisioning_path, exist_ok=True)
|
||||
|
||||
codesigning_data = resolve_codesigning(
|
||||
arguments=arguments,
|
||||
base_path=base_path,
|
||||
build_configuration=build_configuration,
|
||||
provisioning_profiles_path=provisioning_path,
|
||||
additional_codesigning_output_path=additional_codesigning_output_path
|
||||
)
|
||||
if codesigning_data.aps_environment is None:
|
||||
print('Could not find a valid aps-environment entitlement in the provided provisioning profiles')
|
||||
sys.exit(1)
|
||||
|
||||
build_configuration.write_to_variables_file(aps_environment=codesigning_data.aps_environment, path=configuration_repository_path + '/variables.bzl')
|
||||
|
||||
provisioning_profile_files = []
|
||||
for file_name in os.listdir(provisioning_path):
|
||||
if file_name.endswith('.mobileprovision'):
|
||||
provisioning_profile_files.append(file_name)
|
||||
|
||||
with open(provisioning_path + '/BUILD', 'w+') as file:
|
||||
file.write('exports_files([\n')
|
||||
for file_name in provisioning_profile_files:
|
||||
file.write(' "{}",\n'.format(file_name))
|
||||
file.write('])\n')
|
||||
|
||||
if bazel_command_line is not None:
|
||||
bazel_command_line.set_configuration_path(configuration_repository_path)
|
||||
|
||||
|
||||
def generate_project(bazel, arguments):
|
||||
@ -425,7 +496,12 @@ def generate_project(bazel, arguments):
|
||||
|
||||
bazel_command_line.set_continue_on_error(arguments.continueOnError)
|
||||
|
||||
resolve_configuration(bazel_command_line, arguments)
|
||||
resolve_configuration(
|
||||
base_path=os.getcwd(),
|
||||
bazel_command_line=bazel_command_line,
|
||||
arguments=arguments,
|
||||
additional_codesigning_output_path=None
|
||||
)
|
||||
|
||||
bazel_command_line.set_build_number(arguments.buildNumber)
|
||||
|
||||
@ -469,7 +545,12 @@ def build(bazel, arguments):
|
||||
elif arguments.cacheHost is not None:
|
||||
bazel_command_line.add_remote_cache(arguments.cacheHost)
|
||||
|
||||
resolve_configuration(bazel_command_line, arguments)
|
||||
resolve_configuration(
|
||||
base_path=os.getcwd(),
|
||||
bazel_command_line=bazel_command_line,
|
||||
arguments=arguments,
|
||||
additional_codesigning_output_path=None
|
||||
)
|
||||
|
||||
bazel_command_line.set_configuration(arguments.configuration)
|
||||
bazel_command_line.set_build_number(arguments.buildNumber)
|
||||
@ -481,6 +562,38 @@ def build(bazel, arguments):
|
||||
|
||||
bazel_command_line.invoke_build()
|
||||
|
||||
if arguments.outputBuildArtifactsPath is not None:
|
||||
artifacts_path = os.path.abspath(arguments.outputBuildArtifactsPath)
|
||||
if os.path.exists(artifacts_path + '/Telegram.ipa'):
|
||||
os.remove(path)
|
||||
if os.path.exists(artifacts_path + '/DSYMs'):
|
||||
shutil.rmtree(artifacts_path + '/DSYMs')
|
||||
os.makedirs(artifacts_path, exist_ok=True)
|
||||
os.makedirs(artifacts_path + '/DSYMs', exist_ok=True)
|
||||
|
||||
ipa_paths = glob.glob('bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/Telegram.ipa')
|
||||
if len(ipa_paths) == 0:
|
||||
print('Could not find the IPA at bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/Telegram.ipa')
|
||||
sys.exit(1)
|
||||
elif len(ipa_paths) > 1:
|
||||
print('Multiple matching IPA files found: {}'.format(ipa_paths))
|
||||
sys.exit(1)
|
||||
shutil.copyfile(ipa_paths[0], artifacts_path + '/Telegram.ipa')
|
||||
|
||||
dsym_paths = glob.glob('bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/*.dSYM')
|
||||
for dsym_path in dsym_paths:
|
||||
file_name = os.path.basename(dsym_path)
|
||||
shutil.copytree(dsym_path, artifacts_path + '/DSYMs/{}'.format(file_name))
|
||||
previous_directory = os.getcwd()
|
||||
os.chdir(artifacts_path)
|
||||
run_executable_with_output('zip', arguments=[
|
||||
'-r',
|
||||
'Telegram.DSYMs.zip',
|
||||
'./DSYMs'
|
||||
], check_result=True)
|
||||
os.chdir(previous_directory)
|
||||
shutil.rmtree(artifacts_path + '/DSYMs')
|
||||
|
||||
|
||||
def test(bazel, arguments):
|
||||
bazel_command_line = BazelCommandLine(
|
||||
@ -495,7 +608,12 @@ def test(bazel, arguments):
|
||||
elif arguments.cacheHost is not None:
|
||||
bazel_command_line.add_remote_cache(arguments.cacheHost)
|
||||
|
||||
resolve_configuration(bazel_command_line, arguments)
|
||||
resolve_configuration(
|
||||
base_path=os.getcwd(),
|
||||
bazel_command_line=bazel_command_line,
|
||||
arguments=arguments,
|
||||
additional_codesigning_output_path=None
|
||||
)
|
||||
|
||||
bazel_command_line.set_configuration('debug_sim_arm64')
|
||||
bazel_command_line.set_build_number('10000')
|
||||
@ -503,29 +621,64 @@ def test(bazel, arguments):
|
||||
bazel_command_line.invoke_test()
|
||||
|
||||
|
||||
def add_project_and_build_common_arguments(current_parser: argparse.ArgumentParser):
|
||||
group = current_parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument(
|
||||
def add_codesigning_common_arguments(current_parser: argparse.ArgumentParser):
|
||||
configuration_group = current_parser.add_mutually_exclusive_group(required=True)
|
||||
configuration_group.add_argument(
|
||||
'--configurationPath',
|
||||
help='''
|
||||
Path to a folder containing build configuration and provisioning profiles.
|
||||
See build-system/example-configuration for an example.
|
||||
Path to a json containing build configuration.
|
||||
See build-system/appstore-configuration.json for an example.
|
||||
''',
|
||||
metavar='path'
|
||||
)
|
||||
group.add_argument(
|
||||
'--configurationGenerator',
|
||||
|
||||
codesigning_group = current_parser.add_mutually_exclusive_group(required=True)
|
||||
codesigning_group.add_argument(
|
||||
'--gitCodesigningRepository',
|
||||
help='''
|
||||
A command line invocation that will dynamically generate the configuration data
|
||||
(project constants and provisioning profiles).
|
||||
The expression will be parsed according to the shell parsing rules into program and arguments parts.
|
||||
The program will be then invoked with the given arguments plus the path to the output directory.
|
||||
See build-system/generate-configuration.sh for an example.
|
||||
Example: --configurationGenerator="sh ~/my_script.sh argument1"
|
||||
If specified, certificates and provisioning profiles will be loaded from git.
|
||||
TELEGRAM_CODESIGNING_GIT_PASSWORD environment variable must be set.
|
||||
''',
|
||||
metavar='path'
|
||||
)
|
||||
codesigning_group.add_argument(
|
||||
'--codesigningInformationPath',
|
||||
help='''
|
||||
Use signing certificates and provisioning profiles from a local directory.
|
||||
''',
|
||||
metavar='command'
|
||||
)
|
||||
|
||||
current_parser.add_argument(
|
||||
'--gitCodesigningType',
|
||||
choices=[
|
||||
'development',
|
||||
'adhoc',
|
||||
'appstore',
|
||||
'enterprise'
|
||||
],
|
||||
required=False,
|
||||
help='''
|
||||
The name of the folder to use inside "profiles" folder in the git repository.
|
||||
Required if gitCodesigningRepository is specified.
|
||||
''',
|
||||
metavar='type'
|
||||
)
|
||||
|
||||
current_parser.add_argument(
|
||||
'--gitCodesigningAlwaysFetch',
|
||||
action='store_true',
|
||||
required=False,
|
||||
default=True,
|
||||
help='''
|
||||
Always refresh codesigning repository.
|
||||
'''
|
||||
)
|
||||
|
||||
|
||||
def add_project_and_build_common_arguments(current_parser: argparse.ArgumentParser):
|
||||
add_codesigning_common_arguments(current_parser=current_parser)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(prog='Make')
|
||||
@ -575,7 +728,7 @@ if __name__ == '__main__':
|
||||
'--cacheHost',
|
||||
required=False,
|
||||
help='Use remote build artifact cache to speed up rebuilds (See https://github.com/buchgr/bazel-remote).',
|
||||
metavar='http://host:9092'
|
||||
metavar='grpc://host:9092'
|
||||
)
|
||||
cacheTypeGroup.add_argument(
|
||||
'--cacheDir',
|
||||
@ -700,6 +853,40 @@ if __name__ == '__main__':
|
||||
default=False,
|
||||
help='Enable sandbox.',
|
||||
)
|
||||
buildParser.add_argument(
|
||||
'--outputBuildArtifactsPath',
|
||||
required=False,
|
||||
help='Store IPA and DSYM at the specified path after a successful build.',
|
||||
metavar='arguments'
|
||||
)
|
||||
|
||||
remote_build_parser = subparsers.add_parser('remote-build', help='Build the app using a remote environment.')
|
||||
add_codesigning_common_arguments(remote_build_parser)
|
||||
remote_build_parser.add_argument(
|
||||
'--darwinContainersHost',
|
||||
required=True,
|
||||
type=str,
|
||||
help='DarwinContainers host address.'
|
||||
)
|
||||
remote_build_parser.add_argument(
|
||||
'--configuration',
|
||||
choices=[
|
||||
'debug_universal',
|
||||
'debug_arm64',
|
||||
'debug_armv7',
|
||||
'release_arm64',
|
||||
'release_armv7',
|
||||
'release_universal'
|
||||
],
|
||||
required=True,
|
||||
help='Build configuration'
|
||||
)
|
||||
remote_build_parser.add_argument(
|
||||
'--cacheHost',
|
||||
required=False,
|
||||
type=str,
|
||||
help='Bazel remote cache host address.'
|
||||
)
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
parser.print_help()
|
||||
@ -726,6 +913,30 @@ if __name__ == '__main__':
|
||||
generate_project(bazel=bazel_path, arguments=args)
|
||||
elif args.commandName == 'build':
|
||||
build(bazel=bazel_path, arguments=args)
|
||||
elif args.commandName == 'remote-build':
|
||||
base_path = os.getcwd()
|
||||
remote_input_path = '{}/build-input/remote-input'.format(base_path)
|
||||
if os.path.exists(remote_input_path):
|
||||
shutil.rmtree(remote_input_path)
|
||||
os.makedirs(remote_input_path)
|
||||
os.makedirs(remote_input_path + '/certs')
|
||||
os.makedirs(remote_input_path + '/profiles')
|
||||
|
||||
resolve_configuration(
|
||||
base_path=os.getcwd(),
|
||||
bazel_command_line=None,
|
||||
arguments=args,
|
||||
additional_codesigning_output_path=remote_input_path
|
||||
)
|
||||
|
||||
shutil.copyfile(args.configurationPath, remote_input_path + '/configuration.json')
|
||||
|
||||
RemoteBuild.remote_build(
|
||||
darwin_containers_host=args.darwinContainersHost,
|
||||
bazel_cache_host=args.cacheHost,
|
||||
configuration=args.configuration,
|
||||
build_input_data_path=remote_input_path
|
||||
)
|
||||
elif args.commandName == 'test':
|
||||
test(bazel=bazel_path, arguments=args)
|
||||
else:
|
||||
|
||||
141
build-system/Make/RemoteBuild.py
Normal file
141
build-system/Make/RemoteBuild.py
Normal file
@ -0,0 +1,141 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import shlex
|
||||
import tempfile
|
||||
|
||||
from BuildEnvironment import run_executable_with_output
|
||||
|
||||
def session_scp_upload(session, source_path, destination_path):
|
||||
scp_command = 'scp -i {privateKeyPath} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr {source_path} containerhost@"{ipAddress}":{destination_path}'.format(
|
||||
privateKeyPath=session.privateKeyPath,
|
||||
ipAddress=session.ipAddress,
|
||||
source_path=shlex.quote(source_path),
|
||||
destination_path=shlex.quote(destination_path)
|
||||
)
|
||||
if os.system(scp_command) != 0:
|
||||
print('Command {} finished with a non-zero status'.format(scp_command))
|
||||
|
||||
def session_scp_download(session, source_path, destination_path):
|
||||
scp_command = 'scp -i {privateKeyPath} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr containerhost@"{ipAddress}":{source_path} {destination_path}'.format(
|
||||
privateKeyPath=session.privateKeyPath,
|
||||
ipAddress=session.ipAddress,
|
||||
source_path=shlex.quote(source_path),
|
||||
destination_path=shlex.quote(destination_path)
|
||||
)
|
||||
if os.system(scp_command) != 0:
|
||||
print('Command {} finished with a non-zero status'.format(scp_command))
|
||||
|
||||
def session_ssh(session, command):
|
||||
ssh_command = 'ssh -i {privateKeyPath} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null containerhost@"{ipAddress}" -o ServerAliveInterval=60 -t "{command}"'.format(
|
||||
privateKeyPath=session.privateKeyPath,
|
||||
ipAddress=session.ipAddress,
|
||||
command=command
|
||||
)
|
||||
return os.system(ssh_command)
|
||||
|
||||
def remote_build(darwin_containers_host, bazel_cache_host, configuration, build_input_data_path):
|
||||
macos_version = '12.5'
|
||||
|
||||
from darwin_containers import DarwinContainers
|
||||
|
||||
base_dir = os.getcwd()
|
||||
|
||||
configuration_path = 'versions.json'
|
||||
xcode_version = ''
|
||||
with open(configuration_path) as file:
|
||||
configuration_dict = json.load(file)
|
||||
if configuration_dict['xcode'] is None:
|
||||
raise Exception('Missing xcode version in {}'.format(configuration_path))
|
||||
xcode_version = configuration_dict['xcode']
|
||||
|
||||
print('Xcode version: {}'.format(xcode_version))
|
||||
|
||||
commit_count = run_executable_with_output('git', [
|
||||
'rev-list',
|
||||
'--count',
|
||||
'HEAD'
|
||||
])
|
||||
|
||||
build_number_offset = 0
|
||||
with open('build_number_offset') as file:
|
||||
build_number_offset = int(file.read())
|
||||
|
||||
build_number = build_number_offset + int(commit_count)
|
||||
print('Build number: {}'.format(build_number))
|
||||
|
||||
image_name = 'macos-{macos_version}-xcode-{xcode_version}'.format(macos_version=macos_version, xcode_version=xcode_version)
|
||||
|
||||
print('Image name: {}'.format(image_name))
|
||||
|
||||
source_dir = os.path.basename(base_dir)
|
||||
buildbox_dir = 'buildbox'
|
||||
|
||||
transient_data_dir = '{}/transient-data'.format(buildbox_dir)
|
||||
os.makedirs(transient_data_dir, exist_ok=True)
|
||||
|
||||
source_archive_path = '{buildbox_dir}/transient-data/source.tar'.format(buildbox_dir=buildbox_dir)
|
||||
|
||||
if os.path.exists(source_archive_path):
|
||||
os.remove(source_archive_path)
|
||||
|
||||
print('Compressing source code...')
|
||||
os.system('find . -type f -a -not -regex "\\." -a -not -regex ".*\\./git" -a -not -regex ".*\\./git/.*" -a -not -regex "\\./bazel-bin" -a -not -regex "\\./bazel-bin/.*" -a -not -regex "\\./bazel-out" -a -not -regex "\\./bazel-out/.*" -a -not -regex "\\./bazel-testlogs" -a -not -regex "\\./bazel-testlogs/.*" -a -not -regex "\\./bazel-telegram-ios" -a -not -regex "\\./bazel-telegram-ios/.*" -a -not -regex "\\./buildbox" -a -not -regex "\\./buildbox/.*" -a -not -regex "\\./buck-out" -a -not -regex "\\./buck-out/.*" -a -not -regex "\\./\\.buckd" -a -not -regex "\\./\\.buckd/.*" -a -not -regex "\\./build" -a -not -regex "\\./build/.*" -print0 | tar cf "{buildbox_dir}/transient-data/source.tar" --null -T -'.format(buildbox_dir=buildbox_dir))
|
||||
|
||||
darwinContainers = DarwinContainers(serverAddress=darwin_containers_host, verbose=False)
|
||||
|
||||
print('Opening container session...')
|
||||
with darwinContainers.workingImageSession(name=image_name) as session:
|
||||
print('Uploading data to container...')
|
||||
session_scp_upload(session=session, source_path=build_input_data_path, destination_path='telegram-build-input')
|
||||
session_scp_upload(session=session, source_path='{base_dir}/{buildbox_dir}/transient-data/source.tar'.format(base_dir=base_dir, buildbox_dir=buildbox_dir), destination_path='')
|
||||
|
||||
guest_build_sh = '''
|
||||
set -x
|
||||
set -e
|
||||
|
||||
mkdir /Users/Shared/telegram-ios
|
||||
cd /Users/Shared/telegram-ios
|
||||
|
||||
tar -xf $HOME/source.tar
|
||||
|
||||
python3 build-system/Make/ImportCertificates.py --path $HOME/telegram-build-input/certs
|
||||
|
||||
'''
|
||||
|
||||
guest_build_sh += 'python3 build-system/Make/Make.py \\'
|
||||
if bazel_cache_host is not None:
|
||||
guest_build_sh += '--cacheHost="{}" \\'.format(bazel_cache_host)
|
||||
guest_build_sh += 'build \\'
|
||||
guest_build_sh += ''
|
||||
guest_build_sh += '--buildNumber={} \\'.format(build_number)
|
||||
guest_build_sh += '--configuration={} \\'.format(configuration)
|
||||
guest_build_sh += '--configurationPath=$HOME/telegram-build-input/configuration.json \\'
|
||||
guest_build_sh += '--codesigningInformationPath=$HOME/telegram-build-input \\'
|
||||
guest_build_sh += '--outputBuildArtifactsPath=/Users/Shared/telegram-ios/build/artifacts \\'
|
||||
|
||||
guest_build_file_path = tempfile.mktemp()
|
||||
with open(guest_build_file_path, 'w+') as file:
|
||||
file.write(guest_build_sh)
|
||||
session_scp_upload(session=session, source_path=guest_build_file_path, destination_path='guest-build-telegram.sh')
|
||||
os.unlink(guest_build_file_path)
|
||||
|
||||
print('Executing remote build...')
|
||||
|
||||
session_ssh(session=session, command='bash -l guest-build-telegram.sh')
|
||||
|
||||
print('Retrieving build artifacts...')
|
||||
|
||||
artifacts_path='{base_dir}/build/artifacts'.format(base_dir=base_dir)
|
||||
if os.path.exists(artifacts_path):
|
||||
shutil.rmtree(artifacts_path)
|
||||
os.makedirs(artifacts_path, exist_ok=True)
|
||||
|
||||
session_scp_download(session=session, source_path='/Users/Shared/telegram-ios/build/artifacts/*', destination_path='{artifacts_path}/'.format(artifacts_path=artifacts_path))
|
||||
|
||||
if os.path.exists(artifacts_path + '/Telegram.ipa'):
|
||||
print('Artifacts have been stored at {}'.format(artifacts_path))
|
||||
else:
|
||||
print('Telegram.ipa not found')
|
||||
sys.exit(1)
|
||||
14
build-system/appstore-configuration.json
Executable file
14
build-system/appstore-configuration.json
Executable file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"bundle_id": "ph.telegra.Telegraph",
|
||||
"api_id": "8",
|
||||
"api_hash": "7245de8e747a0d6fbe11f7cc14fcc0bb",
|
||||
"team_id": "C67CF9S4VU",
|
||||
"app_center_id": "0",
|
||||
"is_internal_build": "false",
|
||||
"is_appstore_build": "true",
|
||||
"appstore_id": "686449807",
|
||||
"app_specific_url_scheme": "tg",
|
||||
"premium_iap_product_id": "org.telegram.telegramPremium.monthly",
|
||||
"enable_siri": true,
|
||||
"enable_icloud": true
|
||||
}
|
||||
@ -1,358 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
from darwin_containers import DarwinContainers
|
||||
|
||||
def get_clean_env():
|
||||
clean_env = os.environ.copy()
|
||||
return clean_env
|
||||
|
||||
def resolve_executable(program):
|
||||
def is_executable(fpath):
|
||||
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
||||
|
||||
for path in get_clean_env()["PATH"].split(os.pathsep):
|
||||
executable_file = os.path.join(path, program)
|
||||
if is_executable(executable_file):
|
||||
return executable_file
|
||||
return None
|
||||
|
||||
|
||||
def run_executable_with_output(path, arguments):
|
||||
executable_path = resolve_executable(path)
|
||||
if executable_path is None:
|
||||
raise Exception('Could not resolve {} to a valid executable file'.format(path))
|
||||
|
||||
process = subprocess.Popen(
|
||||
[executable_path] + arguments,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
env=get_clean_env()
|
||||
)
|
||||
output_data, _ = process.communicate()
|
||||
output_string = output_data.decode('utf-8')
|
||||
return output_string
|
||||
|
||||
def session_scp_upload(session, source_path, destination_path):
|
||||
scp_command = 'scp -i {privateKeyPath} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr {source_path} containerhost@"{ipAddress}":{destination_path}'.format(
|
||||
privateKeyPath=session.privateKeyPath,
|
||||
ipAddress=session.ipAddress,
|
||||
source_path=shlex.quote(source_path),
|
||||
destination_path=shlex.quote(destination_path)
|
||||
)
|
||||
if os.system(scp_command) != 0:
|
||||
print('Command {} finished with a non-zero status'.format(scp_command))
|
||||
|
||||
def session_ssh(session, command):
|
||||
ssh_command = 'ssh -i {privateKeyPath} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null containerhost@"{ipAddress}" -o ServerAliveInterval=60 -t "{command}"'.format(
|
||||
privateKeyPath=session.privateKeyPath,
|
||||
ipAddress=session.ipAddress,
|
||||
command=command
|
||||
)
|
||||
return os.system(ssh_command)
|
||||
|
||||
def remote_build(darwin_containers_host, configuration):
|
||||
base_dir = os.getcwd()
|
||||
|
||||
configuration_path = 'versions.json'
|
||||
xcode_version = ''
|
||||
with open(configuration_path) as file:
|
||||
configuration_dict = json.load(file)
|
||||
if configuration_dict['xcode'] is None:
|
||||
raise Exception('Missing xcode version in {}'.format(configuration_path))
|
||||
xcode_version = configuration_dict['xcode']
|
||||
|
||||
print('Xcode version: {}'.format(xcode_version))
|
||||
|
||||
commit_count = run_executable_with_output('git', [
|
||||
'rev-list',
|
||||
'--count',
|
||||
'HEAD'
|
||||
])
|
||||
|
||||
build_number_offset = 0
|
||||
with open('build_number_offset') as file:
|
||||
build_number_offset = int(file.read())
|
||||
|
||||
build_number = build_number_offset + int(commit_count)
|
||||
print('Build number: {}'.format(build_number))
|
||||
|
||||
macos_version = '12.5'
|
||||
image_name = 'macos-{macos_version}-xcode-{xcode_version}'.format(macos_version=macos_version, xcode_version=xcode_version)
|
||||
|
||||
print('Image name: {}'.format(image_name))
|
||||
|
||||
buildbox_dir = 'buildbox'
|
||||
os.makedirs('{buildbox_dir}/transient-data'.format(buildbox_dir=buildbox_dir), exist_ok=True)
|
||||
|
||||
codesigning_subpath = ''
|
||||
remote_configuration = ''
|
||||
if configuration == 'appcenter':
|
||||
remote_configuration = 'hockeyapp'
|
||||
elif configuration == 'appstore':
|
||||
remote_configuration = 'appstore'
|
||||
elif configuration == 'reproducible':
|
||||
codesigning_subpath = 'build-system/fake-codesigning'
|
||||
remote_configuration = 'verify'
|
||||
|
||||
destination_codesigning_path = '{buildbox_dir}/transient-data/telegram-codesigning'.format(buildbox_dir=buildbox_dir)
|
||||
destination_build_configuration_path = '{buildbox_dir}/transient-data/build-configuration'.format(buildbox_dir=buildbox_dir)
|
||||
|
||||
if os.path.exists(destination_codesigning_path):
|
||||
shutil.rmtree(destination_codesigning_path)
|
||||
if os.path.exists(destination_build_configuration_path):
|
||||
shutil.rmtree(destination_build_configuration_path)
|
||||
|
||||
shutil.copytree('build-system/fake-codesigning', '{buildbox_dir}/transient-data/telegram-codesigning'.format(buildbox_dir=buildbox_dir))
|
||||
shutil.copytree('build-system/example-configuration', '{buildbox_dir}/transient-data/build-configuration'.format(buildbox_dir=buildbox_dir))
|
||||
else:
|
||||
print('Unknown configuration {}'.format(configuration))
|
||||
sys.exit(1)
|
||||
|
||||
source_dir = os.path.basename(base_dir)
|
||||
source_archive_path = '{buildbox_dir}/transient-data/source.tar'.format(buildbox_dir=buildbox_dir)
|
||||
|
||||
if os.path.exists(source_archive_path):
|
||||
os.remove(source_archive_path)
|
||||
|
||||
print('Compressing source code...')
|
||||
os.system('find . -type f -a -not -regex "\\." -a -not -regex ".*\\./git" -a -not -regex ".*\\./git/.*" -a -not -regex "\\./bazel-bin" -a -not -regex "\\./bazel-bin/.*" -a -not -regex "\\./bazel-out" -a -not -regex "\\./bazel-out/.*" -a -not -regex "\\./bazel-testlogs" -a -not -regex "\\./bazel-testlogs/.*" -a -not -regex "\\./bazel-telegram-ios" -a -not -regex "\\./bazel-telegram-ios/.*" -a -not -regex "\\./buildbox" -a -not -regex "\\./buildbox/.*" -a -not -regex "\\./buck-out" -a -not -regex "\\./buck-out/.*" -a -not -regex "\\./\\.buckd" -a -not -regex "\\./\\.buckd/.*" -a -not -regex "\\./build" -a -not -regex "\\./build/.*" -print0 | tar cf "{buildbox_dir}/transient-data/source.tar" --null -T -'.format(buildbox_dir=buildbox_dir))
|
||||
|
||||
darwinContainers = DarwinContainers(serverAddress=darwin_containers_host, verbose=False)
|
||||
|
||||
print('Opening container session...')
|
||||
with darwinContainers.workingImageSession(name=image_name) as session:
|
||||
print('Uploading data to container...')
|
||||
session_scp_upload(session=session, source_path=codesigning_subpath, destination_path='codesigning_data')
|
||||
session_scp_upload(session=session, source_path='{base_dir}/{buildbox_dir}/transient-data/build-configuration'.format(base_dir=base_dir, buildbox_dir=buildbox_dir), destination_path='telegram-configuration')
|
||||
session_scp_upload(session=session, source_path='{base_dir}/{buildbox_dir}/guest-build-telegram.sh'.format(base_dir=base_dir, buildbox_dir=buildbox_dir), destination_path='')
|
||||
session_scp_upload(session=session, source_path='{base_dir}/{buildbox_dir}/transient-data/source.tar'.format(base_dir=base_dir, buildbox_dir=buildbox_dir), destination_path='')
|
||||
|
||||
print('Executing remote build...')
|
||||
|
||||
bazel_cache_host=''
|
||||
session_ssh(session=session, command='BUILD_NUMBER="{build_number}" BAZEL_HTTP_CACHE_URL="{bazel_cache_host}" bash -l guest-build-telegram.sh {remote_configuration}'.format(
|
||||
build_number=build_number,
|
||||
bazel_cache_host=bazel_cache_host,
|
||||
remote_configuration=remote_configuration
|
||||
))
|
||||
|
||||
print('Retrieving build artifacts...')
|
||||
|
||||
artifacts_path='{base_dir}/build/artifacts'.format(base_dir=base_dir)
|
||||
if os.path.exists(artifacts_path):
|
||||
shutil.rmtree(artifacts_path)
|
||||
os.makedirs(artifacts_path, exist_ok=True)
|
||||
|
||||
session_scp_download(session=session, source_path='telegram-ios/build/artifacts/*', destination_path='{artifacts_path}/'.format(artifacts_path=artifacts_path))
|
||||
print('Artifacts have been stored at {}'.format(artifacts_path))
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(prog='build')
|
||||
|
||||
parser.add_argument(
|
||||
'--verbose',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Print debug info'
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest='commandName', help='Commands')
|
||||
|
||||
remote_build_parser = subparsers.add_parser('remote-build', help='Build the app using a remote environment.')
|
||||
remote_build_parser.add_argument(
|
||||
'--darwinContainersHost',
|
||||
required=True,
|
||||
type=str,
|
||||
help='DarwinContainers host address.'
|
||||
)
|
||||
remote_build_parser.add_argument(
|
||||
'--configuration',
|
||||
choices=[
|
||||
'appcenter',
|
||||
'appstore',
|
||||
'reproducible'
|
||||
],
|
||||
required=True,
|
||||
help='Build configuration'
|
||||
)
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.commandName is None:
|
||||
exit(0)
|
||||
|
||||
if args.commandName == 'remote-build':
|
||||
remote_build(darwin_containers_host=args.darwinContainersHost, configuration=args.configuration)
|
||||
|
||||
|
||||
'''set -e
|
||||
|
||||
rm -f "tools/bazel"
|
||||
cp "$BAZEL" "tools/bazel"
|
||||
|
||||
BUILD_CONFIGURATION="$1"
|
||||
|
||||
if [ "$BUILD_CONFIGURATION" == "hockeyapp" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental-2" ]; then
|
||||
CODESIGNING_SUBPATH="$BUILDBOX_DIR/transient-data/telegram-codesigning/codesigning"
|
||||
elif [ "$BUILD_CONFIGURATION" == "appstore" ] || [ "$BUILD_CONFIGURATION" == "appstore-development" ]; then
|
||||
CODESIGNING_SUBPATH="$BUILDBOX_DIR/transient-data/telegram-codesigning/codesigning"
|
||||
elif [ "$BUILD_CONFIGURATION" == "verify" ]; then
|
||||
CODESIGNING_SUBPATH="build-system/fake-codesigning"
|
||||
else
|
||||
echo "Unknown configuration $1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
COMMIT_COMMENT="$(git log -1 --pretty=%B)"
|
||||
case "$COMMIT_COMMENT" in
|
||||
*"[nocache]"*)
|
||||
export BAZEL_HTTP_CACHE_URL=""
|
||||
;;
|
||||
esac
|
||||
|
||||
COMMIT_ID="$(git rev-parse HEAD)"
|
||||
COMMIT_AUTHOR=$(git log -1 --pretty=format:'%an')
|
||||
if [ -z "$2" ]; then
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
BUILD_NUMBER_OFFSET="$(cat build_number_offset)"
|
||||
COMMIT_COUNT="$(($COMMIT_COUNT+$BUILD_NUMBER_OFFSET))"
|
||||
BUILD_NUMBER="$COMMIT_COUNT"
|
||||
else
|
||||
BUILD_NUMBER="$2"
|
||||
fi
|
||||
|
||||
BASE_DIR=$(pwd)
|
||||
|
||||
if [ "$BUILD_CONFIGURATION" == "hockeyapp" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental-2" ] || [ "$BUILD_CONFIGURATION" == "appstore" ] || [ "$BUILD_CONFIGURATION" == "appstore-development" ]; then
|
||||
if [ ! `which generate-configuration.sh` ]; then
|
||||
echo "generate-configuration.sh not found in PATH $PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning"
|
||||
mkdir -p "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration"
|
||||
|
||||
case "$BUILD_CONFIGURATION" in
|
||||
"hockeyapp"|"appcenter-experimental"|"appcenter-experimental-2")
|
||||
generate-configuration.sh internal release "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning" "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration"
|
||||
;;
|
||||
|
||||
"appstore")
|
||||
generate-configuration.sh appstore release "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning" "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration"
|
||||
;;
|
||||
|
||||
"appstore-development")
|
||||
generate-configuration.sh appstore development "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning" "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown build configuration $BUILD_CONFIGURATION"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
elif [ "$BUILD_CONFIGURATION" == "verify" ]; then
|
||||
mkdir -p "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning"
|
||||
mkdir -p "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration"
|
||||
|
||||
cp -R build-system/fake-codesigning/* "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning/"
|
||||
cp -R build-system/example-configuration/* "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration/"
|
||||
fi
|
||||
|
||||
if [ ! -d "$CODESIGNING_SUBPATH" ]; then
|
||||
echo "$CODESIGNING_SUBPATH does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_DIR=$(basename "$BASE_DIR")
|
||||
rm -f "$BUILDBOX_DIR/transient-data/source.tar"
|
||||
set -x
|
||||
find . -type f -a -not -regex "\\." -a -not -regex ".*\\./git" -a -not -regex ".*\\./git/.*" -a -not -regex "\\./bazel-bin" -a -not -regex "\\./bazel-bin/.*" -a -not -regex "\\./bazel-out" -a -not -regex "\\./bazel-out/.*" -a -not -regex "\\./bazel-testlogs" -a -not -regex "\\./bazel-testlogs/.*" -a -not -regex "\\./bazel-telegram-ios" -a -not -regex "\\./bazel-telegram-ios/.*" -a -not -regex "\\./buildbox" -a -not -regex "\\./buildbox/.*" -a -not -regex "\\./buck-out" -a -not -regex "\\./buck-out/.*" -a -not -regex "\\./\\.buckd" -a -not -regex "\\./\\.buckd/.*" -a -not -regex "\\./build" -a -not -regex "\\./build/.*" -print0 | tar cf "$BUILDBOX_DIR/transient-data/source.tar" --null -T -
|
||||
|
||||
PROCESS_ID="$$"
|
||||
|
||||
if [ -z "$RUNNING_VM" ]; then
|
||||
VM_NAME="$VM_BASE_NAME-$(openssl rand -hex 10)-build-telegram-$PROCESS_ID"
|
||||
else
|
||||
VM_NAME="$RUNNING_VM"
|
||||
fi
|
||||
|
||||
if [ "$BUILD_MACHINE" == "linux" ]; then
|
||||
virt-clone --original "$VM_BASE_NAME" --name "$VM_NAME" --auto-clone
|
||||
virsh start "$VM_NAME"
|
||||
|
||||
echo "Getting VM IP"
|
||||
|
||||
while [ 1 ]; do
|
||||
TEST_IP=$(virsh domifaddr "$VM_NAME" 2>/dev/null | egrep -o 'ipv4.*' | sed -e 's/ipv4\s*//g' | sed -e 's|/.*||g')
|
||||
if [ ! -z "$TEST_IP" ]; then
|
||||
RESPONSE=$(ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$TEST_IP" -o ServerAliveInterval=60 -t "echo -n 1")
|
||||
if [ "$RESPONSE" == "1" ]; then
|
||||
VM_IP="$TEST_IP"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
elif [ "$BUILD_MACHINE" == "macOS" ]; then
|
||||
if [ -z "$RUNNING_VM" ]; then
|
||||
prlctl clone "$VM_BASE_NAME" --linked --name "$VM_NAME"
|
||||
prlctl start "$VM_NAME"
|
||||
|
||||
echo "Getting VM IP"
|
||||
|
||||
while [ 1 ]; do
|
||||
TEST_IP=$(prlctl exec "$VM_NAME" "ifconfig | grep inet | grep broadcast | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1 | tr '\n' '\0'" 2>/dev/null || echo "")
|
||||
if [ ! -z "$TEST_IP" ]; then
|
||||
RESPONSE=$(ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$TEST_IP" -o ServerAliveInterval=60 -t "echo -n 1")
|
||||
if [ "$RESPONSE" == "1" ]; then
|
||||
VM_IP="$TEST_IP"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
echo "VM_IP=$VM_IP"
|
||||
fi
|
||||
|
||||
scp -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr "$CODESIGNING_SUBPATH" telegram@"$VM_IP":codesigning_data
|
||||
scp -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration" telegram@"$VM_IP":telegram-configuration
|
||||
|
||||
scp -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr "$BUILDBOX_DIR/guest-build-telegram.sh" "$BUILDBOX_DIR/transient-data/source.tar" telegram@"$VM_IP":
|
||||
|
||||
ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$VM_IP" -o ServerAliveInterval=60 -t "export BUILD_NUMBER=\"$BUILD_NUMBER\"; export BAZEL_HTTP_CACHE_URL=\"$BAZEL_HTTP_CACHE_URL\"; $GUEST_SHELL -l guest-build-telegram.sh $BUILD_CONFIGURATION" || true
|
||||
|
||||
OUTPUT_PATH="build/artifacts"
|
||||
rm -rf "$OUTPUT_PATH"
|
||||
mkdir -p "$OUTPUT_PATH"
|
||||
|
||||
scp -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr telegram@"$VM_IP":"telegram-ios/build/artifacts/*" "$OUTPUT_PATH/"
|
||||
|
||||
if [ -z "$RUNNING_VM" ]; then
|
||||
if [ "$BUILD_MACHINE" == "linux" ]; then
|
||||
virsh destroy "$VM_NAME"
|
||||
virsh undefine "$VM_NAME" --remove-all-storage --nvram
|
||||
elif [ "$BUILD_MACHINE" == "macOS" ]; then
|
||||
echo "Deleting VM..."
|
||||
#prlctl stop "$VM_NAME" --kill
|
||||
#prlctl delete "$VM_NAME"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f "$OUTPUT_PATH/Telegram.ipa" ]; then
|
||||
exit 1
|
||||
fi
|
||||
'''
|
||||
@ -101,7 +101,8 @@ python3 build-system/Make/Make.py \
|
||||
--configurationPath="$HOME/telegram-configuration" \
|
||||
--buildNumber="$BUILD_NUMBER" \
|
||||
--disableParallelSwiftmoduleGeneration \
|
||||
--configuration="$APP_CONFIGURATION"
|
||||
--configuration="$APP_CONFIGURATION" \
|
||||
--apsEnvironment=production
|
||||
|
||||
OUTPUT_PATH="build/artifacts"
|
||||
rm -rf "$OUTPUT_PATH"
|
||||
|
||||
@ -57,7 +57,7 @@ public func messageMediaImageInteractiveFetched(fetchManager: FetchManager, mess
|
||||
if let range = range {
|
||||
ranges = RangeSet(range.lowerBound ..< range.upperBound)
|
||||
} else {
|
||||
ranges = RangeSet(0 ..< Int64.max)
|
||||
ranges = RangeSet(0 ..< Int64(Int32.max))
|
||||
}
|
||||
return fetchManager.interactivelyFetched(category: .image, location: .chat(messageId.peerId), locationKey: .messageId(messageId), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(resource), ranges: ranges, statsCategory: .image, elevatedPriority: false, userInitiated: userInitiated, priority: priority, storeToDownloadsPeerType: storeToDownloadsPeerType)
|
||||
}
|
||||
|
||||
@ -132,6 +132,10 @@ public final class AppLockContextImpl: AppLockContext {
|
||||
if !strongSelf.lastActiveValue {
|
||||
strongSelf.lastActiveValue = true
|
||||
strongSelf.lastActiveTimestamp = timestamp
|
||||
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: strongSelf.rootPath))), let current = try? JSONDecoder().decode(LockState.self, from: data) {
|
||||
strongSelf.currentStateValue = current
|
||||
}
|
||||
}
|
||||
|
||||
if let lastActiveTimestamp = strongSelf.lastActiveTimestamp {
|
||||
|
||||
@ -482,7 +482,7 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
||||
|
||||
for node in nodes {
|
||||
other.addSubnode(node)
|
||||
node.layer.animateAlpha(from: node.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak node] _ in
|
||||
node.layer.animateAlpha(from: node.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
|
||||
node?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
@ -491,12 +491,11 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
||||
func animateContentIn() {
|
||||
let nodes: [ASDisplayNode] = [
|
||||
self.textNode.textNode,
|
||||
self.iconNode,
|
||||
self.placeholderNode
|
||||
self.iconNode
|
||||
]
|
||||
|
||||
for node in nodes {
|
||||
node.layer.animateAlpha(from: 0.0, to: node.alpha, duration: 0.2)
|
||||
node.layer.animateAlpha(from: 0.0, to: node.alpha, duration: 0.25)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -378,6 +378,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
|
||||
let belowKeyboardOverlayLayout = layout
|
||||
var globalOverlayLayout = layout
|
||||
globalOverlayLayout.inputHeight = nil
|
||||
|
||||
if let globalOverlayBelowKeyboardContainerParent = self.globalOverlayBelowKeyboardContainerParent {
|
||||
if globalOverlayBelowKeyboardContainerParent.view.superview != self.displayNode.view {
|
||||
|
||||
@ -226,7 +226,7 @@ private final class FetchManagerCategoryContext {
|
||||
let storeManager = self.storeManager
|
||||
let parsedRanges: [(Range<Int64>, MediaBoxFetchPriority)]?
|
||||
|
||||
if ranges == RangeSet<Int64>(0 ..< Int64.max) {
|
||||
if ranges == RangeSet<Int64>(0 ..< Int64.max), !"".isEmpty {
|
||||
parsedRanges = nil
|
||||
} else {
|
||||
var resultRanges: [(Range<Int64>, MediaBoxFetchPriority)] = []
|
||||
@ -325,7 +325,7 @@ private final class FetchManagerCategoryContext {
|
||||
isVideoPreload = true
|
||||
}
|
||||
|
||||
if count == 1 && isCompleteRange {
|
||||
if count == 1 && isCompleteRange && !"".isEmpty {
|
||||
parsedRanges = nil
|
||||
} else {
|
||||
var resultRanges: [(Range<Int64>, MediaBoxFetchPriority)] = []
|
||||
|
||||
@ -53,6 +53,9 @@ private func transcribeAudio(path: String, locale: String) -> Signal<Transcripti
|
||||
let _ = try? FileManager.default.copyItem(atPath: path, toPath: tempFilePath)
|
||||
|
||||
let request = SFSpeechURLRecognitionRequest(url: URL(fileURLWithPath: tempFilePath))
|
||||
if #available(iOS 16.0, *) {
|
||||
request.addsPunctuation = true
|
||||
}
|
||||
request.requiresOnDeviceRecognition = speechRecognizer.supportsOnDeviceRecognition
|
||||
request.shouldReportPartialResults = true
|
||||
|
||||
|
||||
@ -574,6 +574,12 @@ public final class MediaBox {
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
self.dataQueue.async {
|
||||
let paths = self.storePathsForId(resource.id)
|
||||
if let _ = fileSize(paths.complete) {
|
||||
subscriber.putCompletion()
|
||||
return
|
||||
}
|
||||
|
||||
guard let (fileContext, releaseContext) = self.fileContext(for: resource.id) else {
|
||||
subscriber.putCompletion()
|
||||
return
|
||||
|
||||
@ -1253,6 +1253,7 @@ final class MediaBoxFileContext {
|
||||
func fetched(range: Range<Int64>, priority: MediaBoxFetchPriority, fetch: @escaping (Signal<[(Range<Int64>, MediaBoxFetchPriority)], NoError>) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>, error: @escaping (MediaResourceDataFetchError) -> Void, completed: @escaping () -> Void) -> Disposable {
|
||||
switch self.content {
|
||||
case .complete:
|
||||
completed()
|
||||
return EmptyDisposable
|
||||
case let .partial(file):
|
||||
return file.fetched(range: range, priority: priority, fetch: fetch, error: error, completed: completed)
|
||||
|
||||
@ -1334,9 +1334,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
for attribute in item.file.attributes {
|
||||
switch attribute {
|
||||
case let .CustomEmoji(_, alt, _):
|
||||
if !alt.isEmpty, let keyword = allEmoticons[alt] {
|
||||
if !item.file.isPremiumEmoji || hasPremium {
|
||||
if !item.file.isPremiumEmoji || hasPremium {
|
||||
if !alt.isEmpty, let keyword = allEmoticons[alt] {
|
||||
result.append((alt, item.file, keyword))
|
||||
} else if alt == query {
|
||||
result.append((alt, item.file, alt))
|
||||
}
|
||||
}
|
||||
default:
|
||||
@ -1345,12 +1347,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
for keyword in keywords {
|
||||
for emoticon in keyword.emoticons {
|
||||
result.append((emoticon, nil, keyword.keyword))
|
||||
}
|
||||
}
|
||||
|
||||
var items: [EmojiPagerContentComponent.Item] = []
|
||||
|
||||
var existingIds = Set<MediaId>()
|
||||
|
||||
@ -730,10 +730,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1660637285] = { return Api.StatsGroupTopPoster.parse_statsGroupTopPoster($0) }
|
||||
dict[-875679776] = { return Api.StatsPercentValue.parse_statsPercentValue($0) }
|
||||
dict[1202287072] = { return Api.StatsURL.parse_statsURL($0) }
|
||||
dict[-50416996] = { return Api.StickerKeyword.parse_stickerKeyword($0) }
|
||||
dict[313694676] = { return Api.StickerPack.parse_stickerPack($0) }
|
||||
dict[768691932] = { return Api.StickerSet.parse_stickerSet($0) }
|
||||
dict[1678812626] = { return Api.StickerSetCovered.parse_stickerSetCovered($0) }
|
||||
dict[451763941] = { return Api.StickerSetCovered.parse_stickerSetFullCovered($0) }
|
||||
dict[1087454222] = { return Api.StickerSetCovered.parse_stickerSetFullCovered($0) }
|
||||
dict[872932635] = { return Api.StickerSetCovered.parse_stickerSetMultiCovered($0) }
|
||||
dict[-1609668650] = { return Api.Theme.parse_theme($0) }
|
||||
dict[-94849324] = { return Api.ThemeSettings.parse_themeSettings($0) }
|
||||
@ -1029,7 +1030,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1802240206] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedFile($0) }
|
||||
dict[1443858741] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedMessage($0) }
|
||||
dict[1705297877] = { return Api.messages.SponsoredMessages.parse_sponsoredMessages($0) }
|
||||
dict[-1240849242] = { return Api.messages.StickerSet.parse_stickerSet($0) }
|
||||
dict[1846886166] = { return Api.messages.StickerSet.parse_stickerSet($0) }
|
||||
dict[-738646805] = { return Api.messages.StickerSet.parse_stickerSetNotModified($0) }
|
||||
dict[904138920] = { return Api.messages.StickerSetInstallResult.parse_stickerSetInstallResultArchive($0) }
|
||||
dict[946083368] = { return Api.messages.StickerSetInstallResult.parse_stickerSetInstallResultSuccess($0) }
|
||||
@ -1584,6 +1585,8 @@ public extension Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StatsURL:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StickerKeyword:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StickerPack:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StickerSet:
|
||||
|
||||
@ -1,3 +1,49 @@
|
||||
public extension Api {
|
||||
enum StickerKeyword: TypeConstructorDescription {
|
||||
case stickerKeyword(documentId: Int64, keyword: [String])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .stickerKeyword(let documentId, let keyword):
|
||||
if boxed {
|
||||
buffer.appendInt32(-50416996)
|
||||
}
|
||||
serializeInt64(documentId, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(keyword.count))
|
||||
for item in keyword {
|
||||
serializeString(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .stickerKeyword(let documentId, let keyword):
|
||||
return ("stickerKeyword", [("documentId", String(describing: documentId)), ("keyword", String(describing: keyword))])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_stickerKeyword(_ reader: BufferReader) -> StickerKeyword? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: [String]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.StickerKeyword.stickerKeyword(documentId: _1!, keyword: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum StickerPack: TypeConstructorDescription {
|
||||
case stickerPack(emoticon: String, documents: [Int64])
|
||||
@ -133,7 +179,7 @@ public extension Api {
|
||||
public extension Api {
|
||||
enum StickerSetCovered: TypeConstructorDescription {
|
||||
case stickerSetCovered(set: Api.StickerSet, cover: Api.Document)
|
||||
case stickerSetFullCovered(set: Api.StickerSet, packs: [Api.StickerPack], documents: [Api.Document])
|
||||
case stickerSetFullCovered(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document])
|
||||
case stickerSetMultiCovered(set: Api.StickerSet, covers: [Api.Document])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
@ -145,9 +191,9 @@ public extension Api {
|
||||
set.serialize(buffer, true)
|
||||
cover.serialize(buffer, true)
|
||||
break
|
||||
case .stickerSetFullCovered(let set, let packs, let documents):
|
||||
case .stickerSetFullCovered(let set, let packs, let keywords, let documents):
|
||||
if boxed {
|
||||
buffer.appendInt32(451763941)
|
||||
buffer.appendInt32(1087454222)
|
||||
}
|
||||
set.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
@ -156,6 +202,11 @@ public extension Api {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(keywords.count))
|
||||
for item in keywords {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(documents.count))
|
||||
for item in documents {
|
||||
item.serialize(buffer, true)
|
||||
@ -179,8 +230,8 @@ public extension Api {
|
||||
switch self {
|
||||
case .stickerSetCovered(let set, let cover):
|
||||
return ("stickerSetCovered", [("set", String(describing: set)), ("cover", String(describing: cover))])
|
||||
case .stickerSetFullCovered(let set, let packs, let documents):
|
||||
return ("stickerSetFullCovered", [("set", String(describing: set)), ("packs", String(describing: packs)), ("documents", String(describing: documents))])
|
||||
case .stickerSetFullCovered(let set, let packs, let keywords, let documents):
|
||||
return ("stickerSetFullCovered", [("set", String(describing: set)), ("packs", String(describing: packs)), ("keywords", String(describing: keywords)), ("documents", String(describing: documents))])
|
||||
case .stickerSetMultiCovered(let set, let covers):
|
||||
return ("stickerSetMultiCovered", [("set", String(describing: set)), ("covers", String(describing: covers))])
|
||||
}
|
||||
@ -213,15 +264,20 @@ public extension Api {
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self)
|
||||
}
|
||||
var _3: [Api.Document]?
|
||||
var _3: [Api.StickerKeyword]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self)
|
||||
}
|
||||
var _4: [Api.Document]?
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.StickerSetCovered.stickerSetFullCovered(set: _1!, packs: _2!, documents: _3!)
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.StickerSetCovered.stickerSetFullCovered(set: _1!, packs: _2!, keywords: _3!, documents: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
||||
@ -638,14 +638,14 @@ public extension Api.messages {
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum StickerSet: TypeConstructorDescription {
|
||||
case stickerSet(set: Api.StickerSet, packs: [Api.StickerPack], documents: [Api.Document])
|
||||
case stickerSet(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document])
|
||||
case stickerSetNotModified
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .stickerSet(let set, let packs, let documents):
|
||||
case .stickerSet(let set, let packs, let keywords, let documents):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1240849242)
|
||||
buffer.appendInt32(1846886166)
|
||||
}
|
||||
set.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
@ -654,6 +654,11 @@ public extension Api.messages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(keywords.count))
|
||||
for item in keywords {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(documents.count))
|
||||
for item in documents {
|
||||
item.serialize(buffer, true)
|
||||
@ -670,8 +675,8 @@ public extension Api.messages {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .stickerSet(let set, let packs, let documents):
|
||||
return ("stickerSet", [("set", String(describing: set)), ("packs", String(describing: packs)), ("documents", String(describing: documents))])
|
||||
case .stickerSet(let set, let packs, let keywords, let documents):
|
||||
return ("stickerSet", [("set", String(describing: set)), ("packs", String(describing: packs)), ("keywords", String(describing: keywords)), ("documents", String(describing: documents))])
|
||||
case .stickerSetNotModified:
|
||||
return ("stickerSetNotModified", [])
|
||||
}
|
||||
@ -686,15 +691,20 @@ public extension Api.messages {
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self)
|
||||
}
|
||||
var _3: [Api.Document]?
|
||||
var _3: [Api.StickerKeyword]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self)
|
||||
}
|
||||
var _4: [Api.Document]?
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.messages.StickerSet.stickerSet(set: _1!, packs: _2!, documents: _3!)
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.messages.StickerSet.stickerSet(set: _1!, packs: _2!, keywords: _3!, documents: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
||||
@ -104,6 +104,7 @@ swift_library(
|
||||
"//submodules/Components/UndoPanelComponent:UndoPanelComponent",
|
||||
"//submodules/Components/HierarchyTrackingLayer:HierarchyTrackingLayer",
|
||||
"//submodules/PeerInfoUI/CreateExternalMediaStreamScreen:CreateExternalMediaStreamScreen",
|
||||
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -41,7 +41,7 @@ public final class CallKitIntegration {
|
||||
}
|
||||
|
||||
func setup(
|
||||
startCall: @escaping (AccountContext, UUID, String, Bool) -> Signal<Bool, NoError>,
|
||||
startCall: @escaping (AccountContext, UUID, EnginePeer.Id?, String, Bool) -> Signal<Bool, NoError>,
|
||||
answerCall: @escaping (UUID) -> Void,
|
||||
endCall: @escaping (UUID) -> Signal<Bool, NoError>,
|
||||
setCallMuted: @escaping (UUID, Bool) -> Void,
|
||||
@ -56,22 +56,17 @@ public final class CallKitIntegration {
|
||||
if !CallKitIntegration.isAvailable {
|
||||
return nil
|
||||
}
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
return nil
|
||||
#else
|
||||
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func startCall(context: AccountContext, peerId: PeerId, isVideo: Bool, displayTitle: String) {
|
||||
func startCall(context: AccountContext, peerId: PeerId, phoneNumber: String?, localContactId: String?, isVideo: Bool, displayTitle: String) {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.startCall(context: context, peerId: peerId, isVideo: isVideo, displayTitle: displayTitle)
|
||||
self.donateIntent(peerId: peerId, displayTitle: displayTitle)
|
||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.startCall(context: context, peerId: peerId, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle)
|
||||
self.donateIntent(peerId: peerId, displayTitle: displayTitle, localContactId: localContactId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,9 +82,9 @@ public final class CallKitIntegration {
|
||||
}
|
||||
}
|
||||
|
||||
public func reportIncomingCall(uuid: UUID, stableId: Int64, handle: String, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) {
|
||||
public func reportIncomingCall(uuid: UUID, stableId: Int64, handle: String, phoneNumber: String?, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.reportIncomingCall(uuid: uuid, stableId: stableId, handle: handle, isVideo: isVideo, displayTitle: displayTitle, completion: completion)
|
||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.reportIncomingCall(uuid: uuid, stableId: stableId, handle: handle, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,10 +94,10 @@ public final class CallKitIntegration {
|
||||
}
|
||||
}
|
||||
|
||||
private func donateIntent(peerId: PeerId, displayTitle: String) {
|
||||
private func donateIntent(peerId: PeerId, displayTitle: String, localContactId: String?) {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
let handle = INPersonHandle(value: "tg\(peerId.id)", type: .unknown)
|
||||
let contact = INPerson(personHandle: handle, nameComponents: nil, displayName: displayTitle, image: nil, contactIdentifier: nil, customIdentifier: "tg\(peerId.id)")
|
||||
let handle = INPersonHandle(value: "tg\(peerId.id._internalGetInt64Value())", type: .unknown)
|
||||
let contact = INPerson(personHandle: handle, nameComponents: nil, displayName: displayTitle, image: nil, contactIdentifier: localContactId, customIdentifier: "tg\(peerId.id._internalGetInt64Value())")
|
||||
|
||||
let intent = INStartAudioCallIntent(contacts: [contact])
|
||||
|
||||
@ -122,8 +117,9 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
|
||||
private var currentStartCallAccount: (UUID, AccountContext)?
|
||||
|
||||
private var alreadyReportedIncomingCalls = Set<UUID>()
|
||||
private var uuidToPeerIdMapping: [UUID: EnginePeer.Id] = [:]
|
||||
|
||||
private var startCall: ((AccountContext, UUID, String, Bool) -> Signal<Bool, NoError>)?
|
||||
private var startCall: ((AccountContext, UUID, EnginePeer.Id?, String, Bool) -> Signal<Bool, NoError>)?
|
||||
private var answerCall: ((UUID) -> Void)?
|
||||
private var endCall: ((UUID) -> Signal<Bool, NoError>)?
|
||||
private var setCallMuted: ((UUID, Bool) -> Void)?
|
||||
@ -141,7 +137,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
|
||||
self.provider.setDelegate(self, queue: nil)
|
||||
}
|
||||
|
||||
func setup(audioSessionActivePromise: ValuePromise<Bool>, startCall: @escaping (AccountContext, UUID, String, Bool) -> Signal<Bool, NoError>, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal<Bool, NoError>, setCallMuted: @escaping (UUID, Bool) -> Void, audioSessionActivationChanged: @escaping (Bool) -> Void) {
|
||||
func setup(audioSessionActivePromise: ValuePromise<Bool>, startCall: @escaping (AccountContext, UUID, EnginePeer.Id?, String, Bool) -> Signal<Bool, NoError>, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal<Bool, NoError>, setCallMuted: @escaping (UUID, Bool) -> Void, audioSessionActivationChanged: @escaping (Bool) -> Void) {
|
||||
self.audioSessionActivePromise = audioSessionActivePromise
|
||||
self.startCall = startCall
|
||||
self.answerCall = answerCall
|
||||
@ -189,10 +185,18 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
|
||||
self.requestTransaction(transaction)
|
||||
}
|
||||
|
||||
func startCall(context: AccountContext, peerId: PeerId, isVideo: Bool, displayTitle: String) {
|
||||
func startCall(context: AccountContext, peerId: PeerId, phoneNumber: String?, isVideo: Bool, displayTitle: String) {
|
||||
let uuid = UUID()
|
||||
self.currentStartCallAccount = (uuid, context)
|
||||
let handle = CXHandle(type: .generic, value: "\(peerId.id._internalGetInt64Value())")
|
||||
let handle: CXHandle
|
||||
if let phoneNumber = phoneNumber {
|
||||
handle = CXHandle(type: .phoneNumber, value: phoneNumber)
|
||||
} else {
|
||||
handle = CXHandle(type: .generic, value: "\(peerId.id._internalGetInt64Value())")
|
||||
}
|
||||
|
||||
self.uuidToPeerIdMapping[uuid] = peerId
|
||||
|
||||
let startCallAction = CXStartCallAction(call: uuid, handle: handle)
|
||||
startCallAction.contactIdentifier = displayTitle
|
||||
|
||||
@ -212,7 +216,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
func reportIncomingCall(uuid: UUID, stableId: Int64, handle: String, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) {
|
||||
func reportIncomingCall(uuid: UUID, stableId: Int64, handle: String, phoneNumber: String?, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) {
|
||||
if self.alreadyReportedIncomingCalls.contains(uuid) {
|
||||
completion?(nil)
|
||||
return
|
||||
@ -220,7 +224,13 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
|
||||
self.alreadyReportedIncomingCalls.insert(uuid)
|
||||
|
||||
let update = CXCallUpdate()
|
||||
update.remoteHandle = CXHandle(type: .generic, value: handle)
|
||||
let nativeHandle: CXHandle
|
||||
if let phoneNumber = phoneNumber {
|
||||
nativeHandle = CXHandle(type: .phoneNumber, value: phoneNumber)
|
||||
} else {
|
||||
nativeHandle = CXHandle(type: .generic, value: handle)
|
||||
}
|
||||
update.remoteHandle = nativeHandle
|
||||
update.localizedCallerName = displayTitle
|
||||
update.supportsHolding = false
|
||||
update.supportsGrouping = false
|
||||
@ -252,7 +262,10 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
|
||||
self.currentStartCallAccount = nil
|
||||
let disposable = MetaDisposable()
|
||||
self.disposableSet.add(disposable)
|
||||
disposable.set((startCall(context, action.callUUID, action.handle.value, action.isVideo)
|
||||
|
||||
let peerId = self.uuidToPeerIdMapping[action.callUUID]
|
||||
|
||||
disposable.set((startCall(context, action.callUUID, peerId, action.handle.value, action.isVideo)
|
||||
|> deliverOnMainQueue
|
||||
|> afterDisposed { [weak self, weak disposable] in
|
||||
if let strongSelf = self, let disposable = disposable {
|
||||
|
||||
@ -13,6 +13,7 @@ import DeviceAccess
|
||||
import UniversalMediaPlayer
|
||||
import AccountContext
|
||||
import DeviceProximity
|
||||
import PhoneNumberFormat
|
||||
|
||||
final class PresentationCallToneRenderer {
|
||||
let queue: Queue
|
||||
@ -607,10 +608,15 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
if previous == nil || previousControl == nil {
|
||||
if !self.reportedIncomingCall, let stableId = sessionState.stableId {
|
||||
self.reportedIncomingCall = true
|
||||
var phoneNumber: String?
|
||||
if let peer = self.peer as? TelegramUser, let phone = peer.phone {
|
||||
phoneNumber = formatPhoneNumber(phone)
|
||||
}
|
||||
self.callKitIntegration?.reportIncomingCall(
|
||||
uuid: self.internalId,
|
||||
stableId: stableId,
|
||||
handle: "\(self.peerId.id._internalGetInt64Value())",
|
||||
phoneNumber: phoneNumber,
|
||||
isVideo: sessionState.type == .video,
|
||||
displayTitle: self.peer?.debugDisplayTitle ?? "Unknown",
|
||||
completion: { [weak self] error in
|
||||
|
||||
@ -11,6 +11,7 @@ import TelegramVoip
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import CallKit
|
||||
import PhoneNumberFormat
|
||||
|
||||
private func callKitIntegrationIfEnabled(_ integration: CallKitIntegration?, settings: VoiceCallSettings?) -> CallKitIntegration? {
|
||||
let enabled = settings?.enableSystemIntegration ?? true
|
||||
@ -128,16 +129,16 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
self.isMediaPlaying = isMediaPlaying
|
||||
self.resumeMediaPlayback = resumeMediaPlayback
|
||||
|
||||
var startCallImpl: ((AccountContext, UUID, String, Bool) -> Signal<Bool, NoError>)?
|
||||
var startCallImpl: ((AccountContext, UUID, EnginePeer.Id?, String, Bool) -> Signal<Bool, NoError>)?
|
||||
var answerCallImpl: ((UUID) -> Void)?
|
||||
var endCallImpl: ((UUID) -> Signal<Bool, NoError>)?
|
||||
var setCallMutedImpl: ((UUID, Bool) -> Void)?
|
||||
var audioSessionActivationChangedImpl: ((Bool) -> Void)?
|
||||
|
||||
self.callKitIntegration = CallKitIntegration.shared
|
||||
self.callKitIntegration?.setup(startCall: { context, uuid, handle, isVideo in
|
||||
self.callKitIntegration?.setup(startCall: { context, uuid, maybePeerId, handle, isVideo in
|
||||
if let startCallImpl = startCallImpl {
|
||||
return startCallImpl(context, uuid, handle, isVideo)
|
||||
return startCallImpl(context, uuid, maybePeerId, handle, isVideo)
|
||||
} else {
|
||||
return .single(false)
|
||||
}
|
||||
@ -215,16 +216,26 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
self?.ringingStatesUpdated(ringingStates, enableCallKit: enableCallKit)
|
||||
})
|
||||
|
||||
startCallImpl = { [weak self] context, uuid, handle, isVideo in
|
||||
if let strongSelf = self, let userId = Int64(handle) {
|
||||
return strongSelf.startCall(context: context, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), isVideo: isVideo, internalId: uuid)
|
||||
|> take(1)
|
||||
|> map { result -> Bool in
|
||||
return result
|
||||
}
|
||||
} else {
|
||||
startCallImpl = { [weak self] context, uuid, maybePeerId, handle, isVideo in
|
||||
guard let strongSelf = self else {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
var peerId: PeerId?
|
||||
if let maybePeerId = maybePeerId {
|
||||
peerId = maybePeerId
|
||||
} else if let userId = Int64(handle) {
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
}
|
||||
guard let peerId = peerId else {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
return strongSelf.startCall(context: context, peerId: peerId, isVideo: isVideo, internalId: uuid)
|
||||
|> take(1)
|
||||
|> map { result -> Bool in
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
answerCallImpl = { [weak self] uuid in
|
||||
@ -398,19 +409,39 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
|> runOn(Queue.mainQueue())
|
||||
let postbox = context.account.postbox
|
||||
strongSelf.startCallDisposable.set((accessEnabledSignal
|
||||
|> mapToSignal { accessEnabled -> Signal<Peer?, NoError> in
|
||||
|> mapToSignal { accessEnabled -> Signal<(Peer?, String?), NoError> in
|
||||
if !accessEnabled {
|
||||
return .single(nil)
|
||||
return .single((nil, nil))
|
||||
}
|
||||
return postbox.transaction { transaction -> (Peer?, String?) in
|
||||
var foundLocalId: String?
|
||||
transaction.enumerateDeviceContactImportInfoItems({ _, value in
|
||||
if let value = value as? TelegramDeviceContactImportedData {
|
||||
switch value {
|
||||
case let .imported(data, _, importedPeerId):
|
||||
if importedPeerId == peerId {
|
||||
foundLocalId = data.localIdentifiers.first
|
||||
return false
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return (transaction.getPeer(peerId), foundLocalId)
|
||||
}
|
||||
return postbox.loadedPeerWithId(peerId)
|
||||
|> take(1)
|
||||
|> map(Optional.init)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
|> deliverOnMainQueue).start(next: { peer, localContactId in
|
||||
guard let strongSelf = self, let peer = peer else {
|
||||
return
|
||||
}
|
||||
strongSelf.callKitIntegration?.startCall(context: context, peerId: peerId, isVideo: isVideo, displayTitle: peer.debugDisplayTitle)
|
||||
var phoneNumber: String?
|
||||
if let peer = peer as? TelegramUser, let phone = peer.phone {
|
||||
phoneNumber = formatPhoneNumber(phone)
|
||||
}
|
||||
strongSelf.callKitIntegration?.startCall(context: context, peerId: peerId, phoneNumber: phoneNumber, localContactId: localContactId, isVideo: isVideo, displayTitle: peer.debugDisplayTitle)
|
||||
}))
|
||||
}
|
||||
if let currentCall = self.currentCall {
|
||||
|
||||
@ -715,6 +715,7 @@ private final class MultipartFetchManager {
|
||||
}
|
||||
|
||||
let part = self.source.request(offset: downloadRange.lowerBound, limit: downloadRange.upperBound - downloadRange.lowerBound, tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground)
|
||||
//|> delay(5.0, queue: self.queue)
|
||||
|> deliverOn(self.queue)
|
||||
let partDisposable = MetaDisposable()
|
||||
self.fetchingParts[downloadRange.lowerBound] = (Int64(downloadRange.count), partDisposable)
|
||||
|
||||
@ -3604,7 +3604,7 @@ func replayFinalState(
|
||||
let namespace: ItemCollectionId.Namespace
|
||||
var items: [ItemCollectionItem] = []
|
||||
let info: StickerPackCollectionInfo
|
||||
if case let .stickerSet(set, packs, documents) = apiSet {
|
||||
if case let .stickerSet(set, packs, keywords, documents) = apiSet {
|
||||
var indexKeysByFile: [MediaId: [MemoryBuffer]] = [:]
|
||||
for pack in packs {
|
||||
switch pack {
|
||||
@ -3621,6 +3621,20 @@ func replayFinalState(
|
||||
break
|
||||
}
|
||||
}
|
||||
for keyword in keywords {
|
||||
switch keyword {
|
||||
case let .stickerKeyword(documentId, texts):
|
||||
for text in texts {
|
||||
let key = ValueBoxKey(text).toMemoryBuffer()
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId)
|
||||
if indexKeysByFile[mediaId] == nil {
|
||||
indexKeysByFile[mediaId] = [key]
|
||||
} else {
|
||||
indexKeysByFile[mediaId]!.append(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for apiDocument in documents {
|
||||
if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id {
|
||||
|
||||
@ -1193,7 +1193,7 @@ public final class AccountViewTracker {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
for result in results {
|
||||
switch result {
|
||||
case let .stickerSet(_, _, documents)?:
|
||||
case let .stickerSet(_, _, _, documents)?:
|
||||
for document in documents {
|
||||
if let file = telegramMediaFileFromApiDocument(document) {
|
||||
if transaction.getMedia(file.fileId) != nil {
|
||||
|
||||
@ -249,7 +249,7 @@ private func pushDeviceContacts(postbox: Postbox, network: Network, importableCo
|
||||
if let updatedData = importableContacts[number] {
|
||||
if let value = value as? TelegramDeviceContactImportedData {
|
||||
switch value {
|
||||
case let .imported(data, _):
|
||||
case let .imported(data, _, _):
|
||||
if data != updatedData {
|
||||
updatedDataIdentifiers.insert(identifier)
|
||||
}
|
||||
@ -289,7 +289,10 @@ private func pushDeviceContacts(postbox: Postbox, network: Network, importableCo
|
||||
outer: for i in (0 ..< orderedPushIdentifiers.count).reversed() {
|
||||
if let user = currentContactDetails[orderedPushIdentifiers[i]], case let .phoneNumber(number) = orderedPushIdentifiers[i], let data = importableContacts[number] {
|
||||
if (user.firstName ?? "") == data.firstName && (user.lastName ?? "") == data.lastName {
|
||||
transaction.setDeviceContactImportInfo(orderedPushIdentifiers[i].key, value: TelegramDeviceContactImportedData.imported(data: data, importedByCount: 0))
|
||||
if data.localIdentifiers.contains("5DFF1D6F-8C0A-48C9-800D-F4BEC59C0E50") {
|
||||
assert(true)
|
||||
}
|
||||
transaction.setDeviceContactImportInfo(orderedPushIdentifiers[i].key, value: TelegramDeviceContactImportedData.imported(data: data, importedByCount: 0, peerId: user.id))
|
||||
orderedPushIdentifiers.remove(at: i)
|
||||
continue outer
|
||||
}
|
||||
@ -334,6 +337,7 @@ private func pushDeviceContactData(postbox: Postbox, network: Network, contacts:
|
||||
var addedContactPeerIds = Set<PeerId>()
|
||||
var retryIndices = Set<Int>()
|
||||
var importedCounts: [Int: Int32] = [:]
|
||||
var peerIdByClientId: [Int64: PeerId] = [:]
|
||||
switch result {
|
||||
case let .importedContacts(imported, popularInvites, retryContacts, users):
|
||||
let peers = users.map { TelegramUser(user: $0) as Peer }
|
||||
@ -342,8 +346,10 @@ private func pushDeviceContactData(postbox: Postbox, network: Network, contacts:
|
||||
})
|
||||
for item in imported {
|
||||
switch item {
|
||||
case let .importedContact(userId, _):
|
||||
addedContactPeerIds.insert(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)))
|
||||
case let .importedContact(userId, clientId):
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
addedContactPeerIds.insert(peerId)
|
||||
peerIdByClientId[clientId] = peerId
|
||||
}
|
||||
}
|
||||
for item in retryContacts {
|
||||
@ -363,7 +369,10 @@ private func pushDeviceContactData(postbox: Postbox, network: Network, contacts:
|
||||
importedData = .retryLater
|
||||
addedReimportAttempts[.phoneNumber(batch[i].0)] = timestamp
|
||||
} else {
|
||||
importedData = .imported(data: batch[i].1, importedByCount: importedCounts[i] ?? 0)
|
||||
if batch[i].1.localIdentifiers.contains("5DFF1D6F-8C0A-48C9-800D-F4BEC59C0E50") {
|
||||
assert(true)
|
||||
}
|
||||
importedData = .imported(data: batch[i].1, importedByCount: importedCounts[i] ?? 0, peerId: peerIdByClientId[Int64(i)])
|
||||
}
|
||||
transaction.setDeviceContactImportInfo(TelegramDeviceContactImportIdentifier.phoneNumber(batch[i].0).key, value: importedData)
|
||||
}
|
||||
|
||||
@ -147,7 +147,7 @@ private func fetchStickerPack(network: Network, info: StickerPackCollectionInfo)
|
||||
switch result {
|
||||
case .stickerSetNotModified:
|
||||
break
|
||||
case let .stickerSet(stickerSet, packs, documents):
|
||||
case let .stickerSet(stickerSet, packs, keywords, documents):
|
||||
updatedInfo = StickerPackCollectionInfo(apiSet: stickerSet, namespace: info.id.namespace)
|
||||
var indexKeysByFile: [MediaId: [MemoryBuffer]] = [:]
|
||||
for pack in packs {
|
||||
@ -165,6 +165,20 @@ private func fetchStickerPack(network: Network, info: StickerPackCollectionInfo)
|
||||
break
|
||||
}
|
||||
}
|
||||
for keyword in keywords {
|
||||
switch keyword {
|
||||
case let .stickerKeyword(documentId, texts):
|
||||
for text in texts {
|
||||
let key = ValueBoxKey(text).toMemoryBuffer()
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId)
|
||||
if indexKeysByFile[mediaId] == nil {
|
||||
indexKeysByFile[mediaId] = [key]
|
||||
} else {
|
||||
indexKeysByFile[mediaId]!.append(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for apiDocument in documents {
|
||||
if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id {
|
||||
@ -224,7 +238,7 @@ private func installRemoteStickerPacks(network: Network, infos: [StickerPackColl
|
||||
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
|
||||
case let .stickerSetMultiCovered(set, _):
|
||||
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
|
||||
case let .stickerSetFullCovered(set, _, _):
|
||||
case let .stickerSetFullCovered(set, _, _, _):
|
||||
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
||||
|
||||
public class Serialization: NSObject, MTSerialization {
|
||||
public func currentLayer() -> UInt {
|
||||
return 146
|
||||
return 147
|
||||
}
|
||||
|
||||
public func parseMessage(_ data: Data!) -> Any! {
|
||||
|
||||
@ -227,7 +227,7 @@ func parsePreviewStickerSet(_ set: Api.StickerSetCovered, namespace: ItemCollect
|
||||
}
|
||||
}
|
||||
return (info, items)
|
||||
case let .stickerSetFullCovered(set, packs, documents):
|
||||
case let .stickerSetFullCovered(set, packs, keywords, documents):
|
||||
var indexKeysByFile: [MediaId: [MemoryBuffer]] = [:]
|
||||
for pack in packs {
|
||||
switch pack {
|
||||
@ -244,6 +244,20 @@ func parsePreviewStickerSet(_ set: Api.StickerSetCovered, namespace: ItemCollect
|
||||
break
|
||||
}
|
||||
}
|
||||
for keyword in keywords {
|
||||
switch keyword {
|
||||
case let .stickerKeyword(documentId, texts):
|
||||
for text in texts {
|
||||
let key = ValueBoxKey(text).toMemoryBuffer()
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId)
|
||||
if indexKeysByFile[mediaId] == nil {
|
||||
indexKeysByFile[mediaId] = [key]
|
||||
} else {
|
||||
indexKeysByFile[mediaId]!.append(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace)
|
||||
var items: [StickerPackItem] = []
|
||||
|
||||
@ -74,7 +74,7 @@ public func addSavedSticker(postbox: Postbox, network: Network, file: TelegramMe
|
||||
switch result {
|
||||
case .stickerSetNotModified:
|
||||
break
|
||||
case let .stickerSet(_, packs, _):
|
||||
case let .stickerSet(_, packs, _, _):
|
||||
var stringRepresentationsByFile: [MediaId: [String]] = [:]
|
||||
for pack in packs {
|
||||
switch pack {
|
||||
|
||||
@ -3,20 +3,24 @@ import Postbox
|
||||
public final class ImportableDeviceContactData: Equatable, PostboxCoding {
|
||||
public let firstName: String
|
||||
public let lastName: String
|
||||
public let localIdentifiers: [String]
|
||||
|
||||
public init(firstName: String, lastName: String) {
|
||||
public init(firstName: String, lastName: String, localIdentifiers: [String]) {
|
||||
self.firstName = firstName
|
||||
self.lastName = lastName
|
||||
self.localIdentifiers = localIdentifiers
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.firstName = decoder.decodeStringForKey("f", orElse: "")
|
||||
self.lastName = decoder.decodeStringForKey("l", orElse: "")
|
||||
self.localIdentifiers = decoder.decodeStringArrayForKey("dis")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.firstName, forKey: "f")
|
||||
encoder.encodeString(self.lastName, forKey: "l")
|
||||
encoder.encodeStringArray(self.localIdentifiers, forKey: "dis")
|
||||
}
|
||||
|
||||
public static func ==(lhs: ImportableDeviceContactData, rhs: ImportableDeviceContactData) -> Bool {
|
||||
@ -26,6 +30,9 @@ public final class ImportableDeviceContactData: Equatable, PostboxCoding {
|
||||
if lhs.lastName != rhs.lastName {
|
||||
return false
|
||||
}
|
||||
if lhs.localIdentifiers != rhs.localIdentifiers {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import Postbox
|
||||
|
||||
public enum TelegramDeviceContactImportedData: PostboxCoding {
|
||||
case imported(data: ImportableDeviceContactData, importedByCount: Int32)
|
||||
case imported(data: ImportableDeviceContactData, importedByCount: Int32, peerId: PeerId?)
|
||||
case retryLater
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
switch decoder.decodeInt32ForKey("_t", orElse: 0) {
|
||||
case 0:
|
||||
self = .imported(data: decoder.decodeObjectForKey("d", decoder: { ImportableDeviceContactData(decoder: $0) }) as! ImportableDeviceContactData, importedByCount: decoder.decodeInt32ForKey("c", orElse: 0))
|
||||
self = .imported(data: decoder.decodeObjectForKey("d", decoder: { ImportableDeviceContactData(decoder: $0) }) as! ImportableDeviceContactData, importedByCount: decoder.decodeInt32ForKey("c", orElse: 0), peerId: decoder.decodeOptionalInt64ForKey("pid").flatMap(PeerId.init))
|
||||
case 1:
|
||||
self = .retryLater
|
||||
default:
|
||||
@ -18,10 +18,15 @@ public enum TelegramDeviceContactImportedData: PostboxCoding {
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
switch self {
|
||||
case let .imported(data, importedByCount):
|
||||
case let .imported(data, importedByCount, peerId):
|
||||
encoder.encodeInt32(0, forKey: "_t")
|
||||
encoder.encodeObject(data, forKey: "d")
|
||||
encoder.encodeInt32(importedByCount, forKey: "c")
|
||||
if let peerId = peerId {
|
||||
encoder.encodeInt64(peerId.toInt64(), forKey: "pid")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "pid")
|
||||
}
|
||||
case .retryLater:
|
||||
encoder.encodeInt32(1, forKey: "_t")
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ func _internal_deviceContactsImportedByCount(postbox: Postbox, contacts: [(Strin
|
||||
for (id, numbers) in contacts {
|
||||
var maxCount: Int32 = 0
|
||||
for number in numbers {
|
||||
if let value = transaction.getDeviceContactImportInfo(TelegramDeviceContactImportIdentifier.phoneNumber(number).key) as? TelegramDeviceContactImportedData, case let .imported(_, importedByCount) = value {
|
||||
if let value = transaction.getDeviceContactImportInfo(TelegramDeviceContactImportIdentifier.phoneNumber(number).key) as? TelegramDeviceContactImportedData, case let .imported(_, importedByCount, _) = value {
|
||||
maxCount = max(maxCount, importedByCount)
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,5 +69,32 @@ public extension TelegramEngine {
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func findPeerByLocalContactIdentifier(identifier: String) -> Signal<EnginePeer?, NoError> {
|
||||
return self.account.postbox.transaction { transaction -> EnginePeer? in
|
||||
var foundPeerId: PeerId?
|
||||
transaction.enumerateDeviceContactImportInfoItems({ _, value in
|
||||
if let value = value as? TelegramDeviceContactImportedData {
|
||||
switch value {
|
||||
case let .imported(data, _, peerId):
|
||||
if data.localIdentifiers.contains(identifier) {
|
||||
if let peerId = peerId {
|
||||
foundPeerId = peerId
|
||||
return false
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if let foundPeerId = foundPeerId {
|
||||
return transaction.getPeer(foundPeerId).flatMap(EnginePeer.init)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,7 +167,7 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
|
||||
switch result {
|
||||
case .stickerSetNotModified:
|
||||
return .complete()
|
||||
case let .stickerSet(set, packs, documents):
|
||||
case let .stickerSet(set, packs, keywords, documents):
|
||||
let namespace: ItemCollectionId.Namespace
|
||||
switch set {
|
||||
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _):
|
||||
@ -195,6 +195,20 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
|
||||
}
|
||||
}
|
||||
}
|
||||
for keyword in keywords {
|
||||
switch keyword {
|
||||
case let .stickerKeyword(documentId, texts):
|
||||
for text in texts {
|
||||
let key = ValueBoxKey(text).toMemoryBuffer()
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId)
|
||||
if indexKeysByFile[mediaId] == nil {
|
||||
indexKeysByFile[mediaId] = [key]
|
||||
} else {
|
||||
indexKeysByFile[mediaId]!.append(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for apiDocument in documents {
|
||||
if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id {
|
||||
|
||||
@ -52,7 +52,7 @@ func updatedRemoteStickerPack(postbox: Postbox, network: Network, reference: Sti
|
||||
switch result {
|
||||
case .stickerSetNotModified:
|
||||
return .complete()
|
||||
case let .stickerSet(set, packs, documents):
|
||||
case let .stickerSet(set, packs, keywords, documents):
|
||||
let namespace: ItemCollectionId.Namespace
|
||||
switch set {
|
||||
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _):
|
||||
@ -80,6 +80,20 @@ func updatedRemoteStickerPack(postbox: Postbox, network: Network, reference: Sti
|
||||
}
|
||||
}
|
||||
}
|
||||
for keyword in keywords {
|
||||
switch keyword {
|
||||
case let .stickerKeyword(documentId, texts):
|
||||
for text in texts {
|
||||
let key = ValueBoxKey(text).toMemoryBuffer()
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId)
|
||||
if indexKeysByFile[mediaId] == nil {
|
||||
indexKeysByFile[mediaId] = [key]
|
||||
} else {
|
||||
indexKeysByFile[mediaId]!.append(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for apiDocument in documents {
|
||||
if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id {
|
||||
|
||||
@ -103,7 +103,7 @@ func _internal_stickerPacksAttachedToMedia(account: Account, media: AnyMediaRefe
|
||||
|> map { result -> [StickerPackReference] in
|
||||
return result.map { pack in
|
||||
switch pack {
|
||||
case let .stickerSetCovered(set, _), let .stickerSetMultiCovered(set, _), let .stickerSetFullCovered(set, _, _):
|
||||
case let .stickerSetCovered(set, _), let .stickerSetMultiCovered(set, _), let .stickerSetFullCovered(set, _, _, _):
|
||||
let info = StickerPackCollectionInfo(apiSet: set, namespace: Namespaces.ItemCollection.CloudStickerPacks)
|
||||
return .id(id: info.id.id, accessHash: info.accessHash)
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ func _internal_requestStickerSet(postbox: Postbox, network: Network, reference:
|
||||
switch result {
|
||||
case .stickerSetNotModified:
|
||||
return .complete()
|
||||
case let .stickerSet(set, packs, documents):
|
||||
case let .stickerSet(set, packs, keywords, documents):
|
||||
info = StickerPackCollectionInfo(apiSet: set, namespace: Namespaces.ItemCollection.CloudStickerPacks)
|
||||
|
||||
switch set {
|
||||
@ -95,6 +95,20 @@ func _internal_requestStickerSet(postbox: Postbox, network: Network, reference:
|
||||
break
|
||||
}
|
||||
}
|
||||
for keyword in keywords {
|
||||
switch keyword {
|
||||
case let .stickerKeyword(documentId, texts):
|
||||
for text in texts {
|
||||
let key = ValueBoxKey(text).toMemoryBuffer()
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId)
|
||||
if indexKeysByFile[mediaId] == nil {
|
||||
indexKeysByFile[mediaId] = [key]
|
||||
} else {
|
||||
indexKeysByFile[mediaId]!.append(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for apiDocument in documents {
|
||||
if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id {
|
||||
@ -167,7 +181,7 @@ func _internal_installStickerSetInteractively(account: Account, info: StickerPac
|
||||
case let .stickerSetMultiCovered(set: set, covers: covers):
|
||||
apiSet = set
|
||||
apiDocuments = covers
|
||||
case let .stickerSetFullCovered(set, _, documents):
|
||||
case let .stickerSetFullCovered(set, _, _, documents):
|
||||
apiSet = set
|
||||
apiDocuments = documents
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ import DebugSettingsUI
|
||||
import BackgroundTasks
|
||||
import UIKitRuntimeUtils
|
||||
import StoreKit
|
||||
import PhoneNumberFormat
|
||||
|
||||
#if canImport(AppCenter)
|
||||
import AppCenter
|
||||
@ -1557,11 +1558,14 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
let phoneNumber = payloadJson["phoneNumber"] as? String
|
||||
|
||||
callKitIntegration.reportIncomingCall(
|
||||
uuid: CallSessionManager.getStableIncomingUUID(stableId: callUpdate.callId),
|
||||
stableId: callUpdate.callId,
|
||||
handle: "\(callUpdate.peer.id.id._internalGetInt64Value())",
|
||||
phoneNumber: phoneNumber.flatMap(formatPhoneNumber),
|
||||
isVideo: false,
|
||||
displayTitle: callUpdate.peer.debugDisplayTitle,
|
||||
completion: { error in
|
||||
@ -1745,52 +1749,65 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
}
|
||||
|
||||
if let contact = startCallContacts.first {
|
||||
var processed = false
|
||||
if let handle = contact.customIdentifier, handle.hasPrefix("tg") {
|
||||
let string = handle.suffix(from: handle.index(handle.startIndex, offsetBy: 2))
|
||||
if let userId = Int64(string) {
|
||||
startCall(userId)
|
||||
processed = true
|
||||
}
|
||||
let contactByIdentifier: Signal<EnginePeer?, NoError>
|
||||
if let context = self.contextValue?.context, let contactIdentifier = contact.contactIdentifier {
|
||||
contactByIdentifier = context.engine.contacts.findPeerByLocalContactIdentifier(identifier: contactIdentifier)
|
||||
} else {
|
||||
contactByIdentifier = .single(nil)
|
||||
}
|
||||
if !processed, let handle = contact.personHandle, let value = handle.value {
|
||||
switch handle.type {
|
||||
case .unknown:
|
||||
if let userId = Int64(value) {
|
||||
startCall(userId)
|
||||
processed = true
|
||||
}
|
||||
case .phoneNumber:
|
||||
let phoneNumber = cleanPhoneNumber(value)
|
||||
if !phoneNumber.isEmpty {
|
||||
guard let context = self.contextValue?.context else {
|
||||
return true
|
||||
|
||||
let _ = (contactByIdentifier |> deliverOnMainQueue).start(next: { peerByContact in
|
||||
var processed = false
|
||||
if let peerByContact = peerByContact {
|
||||
startCall(peerByContact.id.id._internalGetInt64Value())
|
||||
processed = true
|
||||
} else if let handle = contact.customIdentifier, handle.hasPrefix("tg") {
|
||||
let string = handle.suffix(from: handle.index(handle.startIndex, offsetBy: 2))
|
||||
if let userId = Int64(string) {
|
||||
startCall(userId)
|
||||
processed = true
|
||||
}
|
||||
}
|
||||
if !processed, let handle = contact.personHandle, let value = handle.value {
|
||||
switch handle.type {
|
||||
case .unknown:
|
||||
if let userId = Int64(value) {
|
||||
startCall(userId)
|
||||
processed = true
|
||||
}
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.List(includePresences: false))
|
||||
|> map { contactList -> PeerId? in
|
||||
var result: PeerId?
|
||||
for peer in contactList.peers {
|
||||
if case let .user(peer) = peer, let peerPhoneNumber = peer.phone {
|
||||
if matchPhoneNumbers(phoneNumber, peerPhoneNumber) {
|
||||
result = peer.id
|
||||
break
|
||||
case .phoneNumber:
|
||||
let phoneNumber = cleanPhoneNumber(value)
|
||||
if !phoneNumber.isEmpty {
|
||||
guard let context = self.contextValue?.context else {
|
||||
return
|
||||
}
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.List(includePresences: false))
|
||||
|> map { contactList -> PeerId? in
|
||||
var result: PeerId?
|
||||
for peer in contactList.peers {
|
||||
if case let .user(peer) = peer, let peerPhoneNumber = peer.phone {
|
||||
if matchPhoneNumbers(phoneNumber, peerPhoneNumber) {
|
||||
result = peer.id
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return result
|
||||
|> deliverOnMainQueue).start(next: { peerId in
|
||||
if let peerId = peerId {
|
||||
startCall(peerId.id._internalGetInt64Value())
|
||||
}
|
||||
})
|
||||
processed = true
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { peerId in
|
||||
if let peerId = peerId {
|
||||
startCall(peerId.id._internalGetInt64Value())
|
||||
}
|
||||
})
|
||||
processed = true
|
||||
}
|
||||
default:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
} else if let sendMessageIntent = userActivity.interaction?.intent as? INSendMessageIntent {
|
||||
if let contact = sendMessageIntent.recipients?.first, let handle = contact.customIdentifier, handle.hasPrefix("tg") {
|
||||
|
||||
@ -1498,8 +1498,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if packReferences.count > 1 {
|
||||
items.tip = .animatedEmoji(text: presentationData.strings.ChatContextMenu_EmojiSet(Int32(packReferences.count)), arguments: nil, file: nil, action: action)
|
||||
} else if let reference = packReferences.first {
|
||||
items.tipSignal = context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|
||||
|> delay(1.0, queue: .mainQueue())
|
||||
var tipSignal: Signal<LoadedStickerPack, NoError>
|
||||
tipSignal = context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|
||||
|
||||
items.tipSignal = tipSignal
|
||||
|
||||
|> filter { result in
|
||||
if case .result = result {
|
||||
return true
|
||||
|
||||
@ -499,7 +499,10 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
|
||||
var emojiStatus: PeerEmojiStatus?
|
||||
if let user = interfaceState.renderedPeer?.peer as? TelegramUser, let emojiStatusValue = user.emojiStatus {
|
||||
emojiStatus = emojiStatusValue
|
||||
if user.isFake || user.isScam {
|
||||
} else {
|
||||
emojiStatus = emojiStatusValue
|
||||
}
|
||||
}
|
||||
|
||||
/*#if DEBUG
|
||||
|
||||
@ -150,12 +150,12 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
}
|
||||
if peer.id != self.context.account.peerId {
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
|
||||
if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus {
|
||||
titleCredibilityIcon = .emojiStatus(emojiStatus)
|
||||
} else if peer.isFake {
|
||||
if peer.isFake {
|
||||
titleCredibilityIcon = .fake
|
||||
} else if peer.isScam {
|
||||
titleCredibilityIcon = .scam
|
||||
} else if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus {
|
||||
titleCredibilityIcon = .emojiStatus(emojiStatus)
|
||||
} else if peer.isVerified {
|
||||
titleCredibilityIcon = .verified
|
||||
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||
|
||||
@ -429,15 +429,20 @@ private final class DeviceContactDataManagerPrivateImpl {
|
||||
for (stableId, basicData) in self.stableIdToBasicContactData {
|
||||
for phoneNumber in basicData.phoneNumbers {
|
||||
var replace = false
|
||||
var currentLocalIdentifiers: [String] = []
|
||||
if let current = importableContactData[phoneNumber.value] {
|
||||
if stableId < current.0 {
|
||||
replace = true
|
||||
currentLocalIdentifiers = current.1.localIdentifiers
|
||||
}
|
||||
} else {
|
||||
replace = true
|
||||
}
|
||||
if replace {
|
||||
importableContactData[phoneNumber.value] = (stableId, ImportableDeviceContactData(firstName: basicData.firstName, lastName: basicData.lastName))
|
||||
if !currentLocalIdentifiers.contains(stableId) {
|
||||
currentLocalIdentifiers.append(stableId)
|
||||
}
|
||||
importableContactData[phoneNumber.value] = (stableId, ImportableDeviceContactData(firstName: basicData.firstName, lastName: basicData.lastName, localIdentifiers: currentLocalIdentifiers))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2311,13 +2311,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
let credibilityIcon: CredibilityIcon
|
||||
if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus {
|
||||
credibilityIcon = .emojiStatus(emojiStatus)
|
||||
} else if let peer = peer {
|
||||
if let peer = peer {
|
||||
if peer.isFake {
|
||||
credibilityIcon = .fake
|
||||
} else if peer.isScam {
|
||||
credibilityIcon = .scam
|
||||
} else if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus {
|
||||
credibilityIcon = .emojiStatus(emojiStatus)
|
||||
} else if peer.isVerified {
|
||||
credibilityIcon = .verified
|
||||
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled && (peer.id != self.context.account.peerId || self.isSettings) {
|
||||
|
||||
@ -104,6 +104,11 @@ private final class PrefetchManagerInnerImpl {
|
||||
}
|
||||
|
||||
private func updateOrderedPreloadMedia(_ items: [PrefetchMediaItem], automaticDownloadSettings: MediaAutoDownloadSettings, networkType: MediaAutoDownloadNetworkType) {
|
||||
#if DEBUG
|
||||
if "".isEmpty {
|
||||
return
|
||||
}
|
||||
#endif
|
||||
var validIds = Set<MediaId>()
|
||||
var order: Int32 = 0
|
||||
for mediaItem in items {
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#import "NSWeakReference.h"
|
||||
|
||||
|
||||
@interface UIViewControllerPresentingProxy : UIViewController
|
||||
|
||||
@property (nonatomic, copy) void (^dismiss)();
|
||||
@ -139,6 +140,18 @@ static bool notyfyingShiftState = false;
|
||||
|
||||
@end
|
||||
|
||||
@interface UIWindow (Telegram)
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIWindow (Telegram)
|
||||
|
||||
- (instancetype)_65087dc8_initWithFrame:(CGRect)frame {
|
||||
return [self _65087dc8_initWithFrame:frame];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@protocol UIRemoteKeyboardWindowProtocol
|
||||
|
||||
+ (UIWindow * _Nullable)remoteKeyboardWindowForScreen:(UIScreen * _Nullable)screen create:(BOOL)create;
|
||||
@ -161,7 +174,7 @@ static bool notyfyingShiftState = false;
|
||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentViewController:animated:completion:) newSelector:@selector(_65087dc8_presentViewController:animated:completion:)];
|
||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(setNeedsStatusBarAppearanceUpdate) newSelector:@selector(_65087dc8_setNeedsStatusBarAppearanceUpdate)];
|
||||
|
||||
[RuntimeUtils swizzleClassMethodOfClass:NSClassFromString(@"UIRemoteKeyboardWindow") currentSelector:NSSelectorFromString(@"remoteKeyboardWindowForScreen:create:") newSelector:NSSelectorFromString(@"_65087dc8_remoteKeyboardWindowForScreen:create:")];
|
||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIWindow class] currentSelector:@selector(initWithFrame:) newSelector:@selector(_65087dc8_initWithFrame:)];
|
||||
|
||||
if (@available(iOS 15.0, *)) {
|
||||
[RuntimeUtils swizzleInstanceMethodOfClass:[CADisplayLink class] currentSelector:@selector(setPreferredFrameRateRange:) newSelector:@selector(_65087dc8_setPreferredFrameRateRange:)];
|
||||
@ -304,7 +317,8 @@ static bool notyfyingShiftState = false;
|
||||
if (!windowClass) {
|
||||
return nil;
|
||||
}
|
||||
return [(id<UIRemoteKeyboardWindowProtocol>)windowClass remoteKeyboardWindowForScreen:[UIScreen mainScreen] create:false];
|
||||
UIWindow *result = [(id<UIRemoteKeyboardWindowProtocol>)windowClass remoteKeyboardWindowForScreen:[UIScreen mainScreen] create:false];
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "9.0",
|
||||
"bazel": "5.1.0",
|
||||
"xcode": "13.4.1"
|
||||
"xcode": "14.0"
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user