From 5088e394f2ca31298b68f3c7abb36af873fdfef3 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 13 Sep 2023 17:23:18 +1000 Subject: [PATCH 01/12] clean --- Session/Conversations/Message Cells/VisibleMessageCell.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index c264472ea..e1670ea30 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -467,7 +467,6 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { subview.removeFromSuperview() } albumView = nil - albumView = nil bodyTappableLabel = nil // Handle the deleted state first (it's much simpler than the others) From 5f25abc213d1de3b899e83f2b793268feae253f1 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 14 Sep 2023 15:12:37 +1000 Subject: [PATCH 02/12] add paged database observer for link preview attachment --- .../Conversations/ConversationViewModel.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index 0bfef4254..7698a6f25 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -265,6 +265,24 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { .allCases .filter { $0 != .wasRead } ), + PagedData.ObservedChanges( + table: Attachment.self, + columns: [.state], + joinToPagedType: { + let interaction: TypedTableAlias = TypedTableAlias() + let linkPreview: TypedTableAlias = TypedTableAlias() + let linkPreviewAttachment: TypedTableAlias = TypedTableAlias() + + return SQL(""" + LEFT JOIN \(LinkPreview.self) ON ( + \(linkPreview[.url]) = \(interaction[.linkPreviewUrl]) AND + \(Interaction.linkPreviewFilterLiteral()) + ) + LEFT JOIN \(linkPreviewAttachment) ON \(linkPreviewAttachment[.id]) = \(linkPreview[.attachmentId]) + """ + ) + }() + ), PagedData.ObservedChanges( table: Contact.self, columns: [.isTrusted], From b9a5e0befb065c3ee8aadbfd0d37b524f89d0f4d Mon Sep 17 00:00:00 2001 From: vlzuykov Date: Sat, 16 Sep 2023 12:45:41 +0300 Subject: [PATCH 03/12] Add files via upload --- .../Translations/uk.lproj/Localizable.strings | 597 ++++++++++++++++++ 1 file changed, 597 insertions(+) create mode 100644 Session/Meta/Translations/uk.lproj/Localizable.strings diff --git a/Session/Meta/Translations/uk.lproj/Localizable.strings b/Session/Meta/Translations/uk.lproj/Localizable.strings new file mode 100644 index 000000000..c35756587 --- /dev/null +++ b/Session/Meta/Translations/uk.lproj/Localizable.strings @@ -0,0 +1,597 @@ +/* No comment provided by engineer. */ +"ATTACHMENT" = "Додаток"; +/* Title for 'caption' mode of the attachment approval view. */ +"ATTACHMENT_APPROVAL_CAPTION_TITLE" = "Заголовок"; +/* Format string for file extension label in call interstitial view */ +"ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT" = "Тип файлу: %@"; +/* Format string for file size label in call interstitial view. Embeds: {{file size as 'N mb' or 'N kb'}}. */ +"ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT" = "Розмір: %@"; +/* One-line label indicating the user can add no more text to the media message field. */ +"ATTACHMENT_APPROVAL_MESSAGE_LENGTH_LIMIT_REACHED" = "Досягнуто ліміту повідомлення"; +/* Label for 'send' button in the 'attachment approval' dialog. */ +"ATTACHMENT_APPROVAL_SEND_BUTTON" = "Надіслати"; +/* Generic filename for an attachment with no known name */ +"ATTACHMENT_DEFAULT_FILENAME" = "Додаток"; +/* The title of the 'attachment error' alert. */ +"ATTACHMENT_ERROR_ALERT_TITLE" = "Помилка надсилання додатку"; +/* Attachment error message for image attachments which could not be converted to JPEG */ +"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Не вдалося конвертувати зображення."; +/* Attachment error message for video attachments which could not be converted to MP4 */ +"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Не вдається відтворити відео."; +/* Attachment error message for image attachments which cannot be parsed */ +"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Не вдалося проаналізувати зображення."; +/* Attachment error message for image attachments in which metadata could not be removed */ +"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Не вдалося вилучити метадані %d знімків"; +/* Attachment error message for image attachments which could not be resized */ +"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Неможливо змінити розмір зображення."; +/* Attachment error message for attachments whose data exceed file size limits */ +"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Вкладення завелике"; +/* Attachment error message for attachments with invalid data */ +"ATTACHMENT_ERROR_INVALID_DATA" = "Вкладення містить неприпустимий вміст."; +/* Attachment error message for attachments with an invalid file format */ +"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Вкладення має невірний формат файлу."; +/* Attachment error message for attachments without any data */ +"ATTACHMENT_ERROR_MISSING_DATA" = "Вкладення порожнє"; +/* Alert title when picking a document fails for an unknown reason */ +"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Невдала спроба вибору документу."; +/* Alert body when picking a document fails because user picked a directory/bundle */ +"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_BODY" = "Будь ласка, створіть архів цього файлу або директорії та спробуйте надіслати саме його."; +/* Alert title when picking a document fails because user picked a directory/bundle */ +"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_TITLE" = "Файл не підтримується"; +/* Short text label for a voice message attachment, used for thread preview and on the lock screen */ +"ATTACHMENT_TYPE_VOICE_MESSAGE" = "Голосове повідомлення"; +/* Button label for the 'block' button */ +"BLOCK_LIST_BLOCK_BUTTON" = "Заблокувати"; +/* A format for the 'block user' action sheet title. Embeds {{the blocked user's name or phone number}}. */ +"BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Заблокувати %@?"; +/* A format for the 'unblock user' action sheet title. Embeds {{the unblocked user's name or phone number}}. */ +"BLOCK_LIST_UNBLOCK_TITLE_FORMAT" = "Розблокувати %@?"; +/* Button label for the 'unblock' button */ +"BLOCK_LIST_UNBLOCK_BUTTON" = "Розблокувати"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ розблоковано."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Користувача заблоковано"; +/* Alert title after unblocking a group or 1:1 chat. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_UNBLOCKED_ALERT_TITLE_FORMAT" = "%@ розблоковано."; +/* An explanation of the consequences of blocking another user. */ +"BLOCK_USER_BEHAVIOR_EXPLANATION" = "Заблоковані користувачі не зможуть телефонувати тобі або надсилати повідомлення."; +/* Label for generic done button. */ +"BUTTON_DONE" = "Готово"; +/* Button text to enable batch selection mode */ +"BUTTON_SELECT" = "Обрати"; +/* Alert body */ +"CONFIRM_LEAVE_GROUP_DESCRIPTION" = "Ви більше не зможете надсилати або отримувати повідомлення в цій групі."; +/* Alert title */ +"CONFIRM_LEAVE_GROUP_TITLE" = "Ви дійсно хочете покинути цю групу?"; +/* Message for the 'conversation delete confirmation' alert. */ +"CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE" = "Цю дію не буде можливо скасувати."; +/* Title for the 'conversation delete confirmation' alert. */ +"CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE" = "Видалити Бесіду?"; +/* keyboard toolbar label when starting to search with no current results */ +"CONVERSATION_SEARCH_SEARCHING" = "Поиск..."; +/* keyboard toolbar label when no messages match the search string */ +"CONVERSATION_SEARCH_NO_RESULTS" = "Збігів не знайдено"; +/* keyboard toolbar label when exactly 1 message matches the search string */ +"CONVERSATION_SEARCH_ONE_RESULT" = "1 збіг"; +/* keyboard toolbar label when more than 1 message matches the search string. Embeds {{number/position of the 'currently viewed' result}} and the {{total number of results}} */ +"CONVERSATION_SEARCH_RESULTS_FORMAT" = "%d з %d збігів"; +/* table cell label in conversation settings */ +"CONVERSATION_SETTINGS_BLOCK_THIS_USER" = "Заблокувати користувача"; +/* label for 'mute thread' cell in conversation settings */ +"CONVERSATION_SETTINGS_MUTE_LABEL" = "Вимкнути звук"; +/* Table cell label in conversation settings which returns the user to the conversation with 'search mode' activated */ +"CONVERSATION_SETTINGS_SEARCH" = "Пошук в бесідах"; +/* Title for the 'crop/scale image' dialog. */ +"CROP_SCALE_IMAGE_VIEW_TITLE" = "Переміщати і масштабувати"; +/* Subtitle shown while the app is updating its database. */ +"DATABASE_VIEW_OVERLAY_SUBTITLE" = "Це може зайняти кілька хвилин."; +/* Title shown while the app is updating its database. */ +"DATABASE_VIEW_OVERLAY_TITLE" = "Оптимізація бази даних"; +/* The present; the current time. */ +"DATE_NOW" = "Зараз"; +/* table cell label in conversation settings */ +"DISAPPEARING_MESSAGES" = "Зникаючі повідомлення"; +/* table cell label in conversation settings */ +"EDIT_GROUP_ACTION" = "Редагувати групу"; +/* Label indicating media gallery is empty */ +"GALLERY_TILES_EMPTY_GALLERY" = "У вашій розмові немає жодного медіа."; +/* Label indicating loading is in progress */ +"GALLERY_TILES_LOADING_MORE_RECENT_LABEL" = "Завантаження новішої медіа…"; +/* Label indicating loading is in progress */ +"GALLERY_TILES_LOADING_OLDER_LABEL" = "Завантаження старих медіа…"; +/* Error displayed when there is a failure fetching a GIF from the remote service. */ +"GIF_PICKER_ERROR_FETCH_FAILURE" = "Не вдалося отримати запит на GIF. Будь ласка, перевірте, що ви онлайн."; +/* Generic error displayed when picking a GIF */ +"GIF_PICKER_ERROR_GENERIC" = "Невідома помилка"; +/* Shown when selected GIF couldn't be fetched */ +"GIF_PICKER_FAILURE_ALERT_TITLE" = "Не вдалося вибрати GIF"; +/* Alert message shown when user tries to search for GIFs without entering any search terms. */ +"GIF_PICKER_VIEW_MISSING_QUERY" = "Будь ласка, введіть текст для пошуку"; +/* Indicates that an error occurred while searching. */ +"GIF_VIEW_SEARCH_ERROR" = "Помилка. Натисніть для повтор."; +/* Indicates that the user's search had no results. */ +"GIF_VIEW_SEARCH_NO_RESULTS" = "Нічого не знайдено"; +/* No comment provided by engineer. */ +"GROUP_CREATED" = "Група створена."; +/* No comment provided by engineer. */ +"GROUP_MEMBER_JOINED" = "%@ приєдналися до групи. "; +/* No comment provided by engineer. */ +"GROUP_MEMBER_LEFT" = "%@ покинули групу. "; +/* No comment provided by engineer. */ +"GROUP_MEMBER_REMOVED" = "%@ було вилучено із групи. "; +/* No comment provided by engineer. */ +"GROUP_MEMBERS_REMOVED" = "%@ були вилучені із групи. "; +/* No comment provided by engineer. */ +"GROUP_TITLE_CHANGED" = "Заголовок%@'. "; +/* No comment provided by engineer. */ +"GROUP_UPDATED" = "Групу оновлено."; +/* No comment provided by engineer. */ +"GROUP_YOU_LEFT" = "Ви покинули групу"; +/* No comment provided by engineer. */ +"YOU_WERE_REMOVED" = " Вас вилучили з групи. "; +/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */ +"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "Ви не можете надсилати більше, ніж %@ файлів."; +/* alert title */ +"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Не вдалося видалити вкладення"; +/* Message for the alert indicating that an audio file is invalid. */ +"INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE" = "Недійсний формат аудіо"; +/* Confirmation button within contextual alert */ +"LEAVE_BUTTON_TITLE" = "Покинути"; +/* table cell label in conversation settings */ +"LEAVE_GROUP_ACTION" = "Вийти з групи"; +/* nav bar button item */ +"MEDIA_DETAIL_VIEW_ALL_MEDIA_BUTTON" = "Всі медіа"; +/* media picker option to choose from library */ +"MEDIA_FROM_LIBRARY_BUTTON" = "Галерея"; +/* Confirmation button text to delete selected media from the gallery, embeds {{number of messages}} */ +"MEDIA_GALLERY_DELETE_MULTIPLE_MESSAGES_FORMAT" = "Видалити %d повідомлень"; +/* Confirmation button text to delete selected media message from the gallery */ +"MEDIA_GALLERY_DELETE_SINGLE_MESSAGE" = "Видалити повідомлення"; +/* embeds {{sender name}} and {{sent datetime}}, e.g. 'Sarah on 10/30/18, 3:29' */ +"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ в %@"; +/* Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}. */ +"MEDIA_GALLERY_MORE_ITEMS_FORMAT" = "+%@"; +/* Short sender label for media sent by you */ +"MEDIA_GALLERY_SENDER_NAME_YOU" = "ти"; +/* Section header in media gallery collection view */ +"MEDIA_GALLERY_THIS_MONTH_HEADER" = "Цього місяця"; +/* status message for failed messages */ +"MESSAGE_STATUS_FAILED" = "Помилка надсилання"; +/* status message for read messages */ +"MESSAGE_STATUS_READ" = "Прочитано"; +/* message status while message is sending. */ +"MESSAGE_STATUS_SENDING" = "Надсилання…"; +/* status message for sent messages */ +"MESSAGE_STATUS_SENT" = "Надіслано"; +/* status message while attachment is uploading */ +"MESSAGE_STATUS_UPLOADING" = "завантаження…"; +/* notification title. Embeds {{author name}} and {{group name}} */ +"NEW_GROUP_MESSAGE_NOTIFICATION_TITLE" = "%@ в %@"; +/* Label for 1:1 conversation with yourself. */ +"NOTE_TO_SELF" = "Нотатка для себе"; +/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */ +"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "Можливо, під час перезавантаження вашого %@ ви отримали повідомлення."; +/* No comment provided by engineer. */ +"BUTTON_OK" = "Ок"; +/* Info Message when {{other user}} disables or doesn't support disappearing messages */ +"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ вимкнув зникаючі повідомлення."; +/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */ +"OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ встановив/ла таймер зникаючих повіомлень на %@"; +/* alert title, generic error preventing user from capturing a photo */ +"PHOTO_CAPTURE_GENERIC_ERROR" = "Не вдається зробити знімок."; +/* alert title */ +"PHOTO_CAPTURE_UNABLE_TO_CAPTURE_IMAGE" = "Не вдається зробити знімок."; +/* alert title */ +"PHOTO_CAPTURE_UNABLE_TO_INITIALIZE_CAMERA" = "Не вдалося налаштувати камеру."; +/* label for system photo collections which have no name. */ +"PHOTO_PICKER_UNNAMED_COLLECTION" = "Альбом без імені"; +/* Notification action button title */ +"PUSH_MANAGER_MARKREAD" = "Позначити прочитаним"; +/* Notification action button title */ +"PUSH_MANAGER_REPLY" = "Відповісти"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Автентифікуйтесь для відкриття Session."; +/* Title for alert indicating that screen lock could not be unlocked. */ +"SCREEN_LOCK_UNLOCK_FAILED" = "Помилка автентифікації"; +/* alert title when user attempts to leave the send media flow when they have an in-progress album */ +"SEND_MEDIA_ABANDON_TITLE" = "Відхилити медіа?"; +/* alert action, confirming the user wants to exit the media flow and abandon any photos they've taken */ +"SEND_MEDIA_CONFIRM_ABANDON_ALBUM" = "Відхилити медіа"; +/* Format string for the default 'Note' sound. Embeds the system {{sound name}}. */ +"SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT" = "%@ (за замовчуванням)"; +/* Label for settings view that allows user to change the notification sound. */ +"SETTINGS_ITEM_NOTIFICATION_SOUND" = "Звук повідомлення"; +/* Label for the 'no sound' option that allows users to disable sounds for notifications, etc. */ +"SOUNDS_NONE" = "Жодного"; +/* {{number of days}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 days}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_DAYS" = "%@ днів"; +/* Label text below navbar button, embeds {{number of days}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5d' not '5 d'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_DAYS_SHORT_FORMAT" = "%@д"; +/* {{number of hours}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 hours}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_HOURS" = "%@ годин"; +/* Label text below navbar button, embeds {{number of hours}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5h' not '5 h'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_HOURS_SHORT_FORMAT" = "%@г"; +/* {{number of minutes}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 minutes}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_MINUTES" = "%@ хвилин"; +/* Label text below navbar button, embeds {{number of minutes}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5m' not '5 m'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_MINUTES_SHORT_FORMAT" = "%@хв"; +/* {{number of seconds}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 seconds}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SECONDS" = "%@ секунд"; +/* Label text below navbar button, embeds {{number of seconds}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5s' not '5 s'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SECONDS_SHORT_FORMAT" = "%@с"; +/* {{1 day}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 day}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_DAY" = "%@ день"; +/* {{1 hour}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 hour}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_HOUR" = "%@ година"; +/* {{1 minute}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 minute}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_MINUTE" = "%@ хвилина"; +/* {{1 week}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 week}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_WEEK" = "%@ тиждень"; +/* {{number of weeks}}, embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 weeks}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_WEEKS" = "%@ тижнів"; +/* Label text below navbar button, embeds {{number of weeks}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5w' not '5 w'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_WEEKS_SHORT_FORMAT" = "%@т"; +/* Label for the cancel button in an alert or action sheet. */ +"TXT_CANCEL_TITLE" = "Скасувати"; +/* No comment provided by engineer. */ +"TXT_DELETE_TITLE" = "Видалити"; +/* Filename for voice messages. */ +"VOICE_MESSAGE_FILE_NAME" = "Голосове повідомлення"; +/* Message for the alert indicating the 'voice message' needs to be held to be held down to record. */ +"VOICE_MESSAGE_TOO_SHORT_ALERT_MESSAGE" = "Торкніться та утримуйте, щоб записати голосове повідомлення."; +/* Title for the alert indicating the 'voice message' needs to be held to be held down to record. */ +"VOICE_MESSAGE_TOO_SHORT_ALERT_TITLE" = "Голосове повідомлення"; +/* Info Message when you disable disappearing messages */ +"YOU_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "Ви вимкнули зникаючі повідомлення."; +/* Info message embedding a {{time amount}}, see the *_TIME_AMOUNT strings for context. */ +"YOU_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "Ви встановили таймер зникаючих повідомлень на %@"; +// MARK: - Session +"continue_2" = "Продовжити"; +"copy" = "Скопіювати"; +"invalid_url" = "Недійсне URL-посилання"; +"next" = "Далі"; +"share" = "Поділитися"; +"invalid_session_id" = "Невірний Session ID"; +"cancel" = "Скасувати"; +"your_session_id" = "Ваш Session ID"; +"vc_landing_title_2" = "Ваша сесія починається тут..."; +"vc_landing_register_button_title" = "Створити Session ID"; +"vc_landing_restore_button_title" = "Увійти в акаунт"; +"vc_landing_link_button_title" = "Прив'язати пристрій"; +"view_fake_chat_bubble_1" = "Що таке Session?"; +"view_fake_chat_bubble_2" = "Це децентралізований, шифрований месенджер"; +"view_fake_chat_bubble_3" = "Тобто він не збирає мою особисту інформацію чи метадані моєї розмови? Як це працює?"; +"view_fake_chat_bubble_4" = "Він використовує комбінацію вдосконаленої анонімної мережі та технологій наскрізного шифрування."; +"view_fake_chat_bubble_5" = "Друзі не дозволять друзям користуватися месенджерами з сумнівною безпекою. Ласкаво прошу."; +"vc_register_title" = "Привітайтесь з вашим Session ID"; +"vc_register_explanation" = "Ваш Session ID є унікальною адресою, використовуючи яку люди зможуть зв’язатися з вами в Session. Зважаючи на те, що зв'язок з вашою реальною особистістю відсутній, Session ID задуманий та реалізований абсолютно анонімним та приватним."; +"vc_restore_title" = "Відновити обліковий запис"; +"vc_restore_explanation" = "Введіть фразу відновлення, яку вам було надано під час реєстрації, для відновлення облікового запису."; +"vc_restore_seed_text_field_hint" = "Введіть свою фразу відновлення"; +"vc_link_device_title" = "Прив'язати пристрій"; +"vc_link_device_scan_qr_code_tab_title" = "Сканувати QR-код"; +"vc_display_name_title_2" = "Виберіть ваш псевдонім для показу"; +"vc_display_name_explanation" = "Це буде ваше ім’я під час використання Session. Це може бути ваше справжнє ім’я, псевдонім або будь-що інше, що вам подобається."; +"vc_display_name_text_field_hint" = "Введіть ім'я для показу"; +"vc_display_name_display_name_missing_error" = "Будь ласка, виберіть псевдонім для показу"; +"vc_display_name_display_name_too_long_error" = "Будь ласка, виберіть коротший псевдонім для показу"; +"vc_pn_mode_recommended_option_tag" = "Рекомендовано"; +"vc_pn_mode_no_option_picked_modal_title" = "Будь ласка, виберіть варіант"; +"vc_home_empty_state_message" = "У вас ще немає жодних контактів"; +"vc_home_empty_state_button_title" = "Почати розмову"; +"vc_seed_title" = "Ваша фраза відновлення"; +"vc_seed_title_2" = "Знайомтесь з вашою фразою відновлення"; +"vc_seed_explanation" = "Ваша фраза відновлення — це головний ключ до вашого Session ID. Ви можете використовувати її для відновлення вашого Session ID якщо ви втратите доступ до пристрою. Зберігайте вашу фразу відновлення у безпечному місці та не надавайте її нікому."; +"vc_seed_reveal_button_title" = "Натисніть, щоб відкрити"; +"view_seed_reminder_subtitle_1" = "Захистіть свій обліковий запис, зберігши відновлювальну фразу"; +"view_seed_reminder_subtitle_2" = "Натисніть і утримуйте приховані слова, щоб відкрити фразу для відновлення, а потім збережіть її в безпечному місці, щоб захистити свій Session ID."; +"view_seed_reminder_subtitle_3" = "Переконайтеся, що ви зберегли свою фразу відновлення в безпечному місці"; +"vc_path_title" = "Шлях"; +"vc_path_explanation" = "Session приховує вашу IP-адресу, перенаправляючи ваші повідомлення через декілька сервісних вузлів в децентралізованій мережі Session. Це країни, через які зараз перенаправляється ваше з'єднання:"; +"vc_path_device_row_title" = "Ви"; +"vc_path_guard_node_row_title" = "Вхідний вузол"; +"vc_path_service_node_row_title" = "Сервісний вузол"; +"vc_path_destination_row_title" = "Місце призначення"; +"vc_path_learn_more_button_title" = "Дізнатися більше"; +"vc_create_private_chat_title" = "Нове повідомлення"; +"vc_create_private_chat_enter_session_id_tab_title" = "Введіть Session ID"; +"vc_create_private_chat_scan_qr_code_tab_title" = "Сканувати QR-код"; +"vc_enter_public_key_explanation" = "Почніть нову розмову, ввівши чийсь Session ID або поділіться з ним своїм Session ID."; +"vc_scan_qr_code_camera_access_explanation" = "Session потрібен дозвіл до камери, щоб сканувати QR-код"; +"vc_scan_qr_code_grant_camera_access_button_title" = "Дозволити доступ до камери"; +"vc_create_closed_group_title" = "Створити групу"; +"vc_create_closed_group_text_field_hint" = "Введіть назву групи"; +"vc_create_closed_group_empty_state_message" = "У вас ще немає жодних контактів"; +"vc_create_closed_group_empty_state_button_title" = "Почати розмову"; +"vc_create_closed_group_group_name_missing_error" = "Будь ласка, введіть назву групи"; +"vc_create_closed_group_group_name_too_long_error" = "Будь ласка, введіть коротшу назву групи"; +"vc_create_closed_group_too_many_group_members_error" = "Закрита група не може мати більше 100 учасників"; +"vc_join_public_chat_title" = "Приєднатися до спільноти"; +"vc_join_public_chat_enter_group_url_tab_title" = "URL-адреса спільноти"; +"vc_join_public_chat_scan_qr_code_tab_title" = "Сканувати QR-код"; +"vc_enter_chat_url_text_field_hint" = "Введіть URL-адресу спільноти"; +"vc_settings_title" = "Налаштування"; +"vc_group_settings_title" = "Налаштування групи"; +"vc_settings_display_name_missing_error" = "Будь ласка, виберіть псевдонім для показу"; +"vc_settings_display_name_too_long_error" = "Будь ласка, виберіть коротший псевдонім для показу"; +"vc_settings_privacy_button_title" = "Конфіденційність"; +"vc_settings_notifications_button_title" = "Сповіщення"; +"vc_settings_recovery_phrase_button_title" = "Фраза відновлення"; +"vc_settings_clear_all_data_button_title" = "Очистити дані"; +"vc_qr_code_title" = "QR-код"; +"vc_qr_code_view_my_qr_code_tab_title" = "Переглянути мій QR-код"; +"vc_qr_code_view_scan_qr_code_tab_title" = "Сканувати QR-код"; +"vc_qr_code_view_scan_qr_code_explanation" = "Проскануйте чийсь QR-код, щоб почати розмову з ними"; +"vc_view_my_qr_code_explanation" = "Це ваш QR-код. Інші користувачі можуть просканувати його, щоб почати розмову з вами."; +// MARK: - Not Yet Translated +"fast_mode_explanation" = "Ви отримуватимете сповіщення про нові повідомлення надійно та одразу за допомогою серверів сповіщень Apple."; +"fast_mode" = "Швидкий режим"; +"slow_mode_explanation" = "Session буде періодично перевіряти наявність нових повідомлень у фоновому режимі."; +"slow_mode" = "Повільний режим"; +"vc_pn_mode_title" = "Сповіщення про повідомлення"; +"vc_link_device_recovery_phrase_tab_title" = "Фраза відновлення"; +"vc_link_device_scan_qr_code_explanation" = "Перейдіть до Налаштувань → Фраза відновлення на вашому іншому пристрої, щоб показати ваш QR-код."; +"vc_enter_recovery_phrase_title" = "Фраза відновлення"; +"vc_enter_recovery_phrase_explanation" = "Для прив'язки пристрою ведіть фразу відновлення, яка була надана вам під час реєстрації."; +"vc_enter_public_key_text_field_hint" = "Введіть Session ID або ім'я ONS"; +"admin_group_leave_warning" = "Оскільки ви створили цю групу, вона буде видалена для всіх. Це не можна буде скасувати."; +"vc_join_open_group_suggestions_title" = "Або приєднуйтесь до однієї з цих..."; +"vc_settings_invite_a_friend_button_title" = "Запросити друга"; +"copied" = "Скопійовано"; +"vc_conversation_settings_copy_session_id_button_title" = "Скопіювати Session ID"; +"vc_conversation_input_prompt" = "Написати"; +"vc_conversation_voice_message_cancel_message" = "Проведіть, щоб скасувати"; +"modal_download_attachment_title" = "Довіряти %@?"; +"modal_download_attachment_explanation" = "Чи дійсно ви хочете звантажити медіафайл, відправлений %@?"; +"modal_download_button_title" = "Завантажити"; +"modal_open_url_title" = "Відкрити URL-адресу?"; +"modal_open_url_explanation" = "Ви впевнені, що хочете відкрити %@?"; +"modal_open_url_button_title" = "Відкрити"; +"modal_copy_url_button_title" = "Копіювати посилання"; +"modal_blocked_title" = "Розблокувати %@?"; +"modal_blocked_explanation" = "Ви впевнені, що бажаєте розблокувати %@?"; +"modal_blocked_button_title" = "Розблокувати"; +"modal_link_previews_title" = "Генерувати попередній перегляд посилань?"; +"modal_link_previews_explanation" = "Увімкнення попереднього перегляду посилань призведе до показу попереднього перегляду тих посилань, які ви надсилаєте та отримуєте. Це може бути корисним, але Session потрібно буде зв'язуватися з веб-сайтами для створення попереднього перегляду. Ви завжди зможете вимкнути попередній перегляд посилань в налаштуваннях Session."; +"modal_link_previews_button_title" = "Увімкнути"; +"vc_share_title" = "Поділитися в Session"; +"vc_share_loading_message" = "Підготовка вкладень..."; +"vc_share_sending_message" = "Надсилання..."; +"vc_share_link_previews_unsecure" = "Попередній перегляд не завантажений для незахищеного посилання"; +"vc_share_link_previews_error" = "Неможливо завантажити попередній перегляд"; +"vc_share_link_previews_disabled_title" = "Попередній перегляд посилань вимкнено"; +"vc_share_link_previews_disabled_explanation" = "Увімкнення попереднього перегляду посилань призведе до показу попереднього перегляду тих посилань, якими ви ділитесь. Це може бути корисним, але Session потрібно буде зв'язуватися з веб-сайтами для створення попереднього перегляду.\n\nВи зможете ввімкнути попередній перегляд посилань в налаштуваннях Session."; +"view_open_group_invitation_description" = "Запрошення до відкритої групи"; +"vc_conversation_settings_invite_button_title" = "Додати учасників"; +"modal_send_seed_title" = "Попередження"; +"modal_send_seed_explanation" = "Це ваша фраза відновлення. Якщо ви надішлете її комусь, він матиме повний доступ до вашого облікового запису."; +"modal_send_seed_send_button_title" = "Надіслати"; +"vc_conversation_settings_notify_for_mentions_only_title" = "Сповіщати лише про згадки"; +"vc_conversation_settings_notify_for_mentions_only_explanation" = "Коли увімкнено, ви будете отримувати сповіщення лише про повідомлення, в яких згадують вас."; +"view_conversation_title_notify_for_mentions_only" = "Сповіщення лише про згадки"; +"message_deleted" = "Це повідомлення було видалено"; +"delete_message_for_me" = "Видалити лише для мене"; +"delete_message_for_everyone" = "Видалити для всіх"; +"delete_message_for_me_and_recipient" = "Видалити для мене та %@"; +"context_menu_reply" = "Відповісти"; +"context_menu_save" = "Зберегти"; +"context_menu_ban_user" = "Додати користувача до чорного списку"; +"context_menu_ban_and_delete_all" = "Додати до чорного списку та видалити всіх"; +"context_menu_ban_user_error_alert_message" = "Неможливо заблокувати користувача"; +"accessibility_expanding_attachments_button" = "Додати вкладення"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Документ"; +"accessibility_library_button" = "Галерея фотографій"; +"accessibility_camera_button" = "Камера"; +"accessibility_main_button_collapse" = "Згорнути параметри вкладень"; +"invalid_recovery_phrase" = "Невірна фраза відновлення"; +"DISMISS_BUTTON_TEXT" = "Відхилити"; +/* Button text which opens the settings app */ +"OPEN_SETTINGS_BUTTON" = "Налаштування"; +"call_outgoing" = "Ви дзвонили %@"; +"call_incoming" = "%@ дзвонив вам"; +"call_missed" = "Пропущений дзвінок від %@"; +"APN_Message" = "Ви отримали нове повідомлення."; +"APN_Collapsed_Messages" = "Ви отримали %@ нових повідомлень."; +"PIN_BUTTON_TEXT" = "Закріпити"; +"UNPIN_BUTTON_TEXT" = "Відкріпити"; +"modal_call_missed_tips_title" = "Дзвінок пропущено"; +"modal_call_missed_tips_explanation" = "Ви пропустили дзвінок від %@, оскільки вам потрібно ввімкнути дозвіл «Голосові та відеодзвінки» в налаштуваннях конфіденційності."; +"media_saved" = "%@ зберіг медіафайл."; +"screenshot_taken" = "%@ зробив знімок екрану."; +"SEARCH_SECTION_CONTACTS" = "Контакти та Групи"; +"SEARCH_SECTION_MESSAGES" = "Повідомлення"; +"MESSAGE_REQUESTS_TITLE" = "Запити на повідомлення"; +"MESSAGE_REQUESTS_EMPTY_TEXT" = "Немає незавершених запитів на повідомлення"; +"MESSAGE_REQUESTS_CLEAR_ALL" = "Очистити всі"; +"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Ви впевнені, що бажаєте очистити всі запити на повідомлення?"; +"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Очистити"; +"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Ви дійсно бажаєте видалити цей запит?"; +"MESSAGE_REQUESTS_BLOCK_CONFIRMATION_ACTON" = "Ви дійсно хочете заблокувати цей контакт?"; +"MESSAGE_REQUESTS_INFO" = "Надсилання повідомлення цьому користувачеві автоматично прийме його запит на повідомлення та розкриє ваш Session ID."; +"MESSAGE_REQUESTS_ACCEPTED" = "Ваш запит на повідомлення прийнято."; +"MESSAGE_REQUESTS_NOTIFICATION" = "У вас є новий запит на повідомлення"; +"TXT_HIDE_TITLE" = "Приховати"; +"TXT_DELETE_ACCEPT" = "Прийняти"; +"TXT_DECLINE_TITLE" = "Відхилити"; +"TXT_BLOCK_USER_TITLE" = "Заблокувати користувача"; +"ALERT_ERROR_TITLE" = "Помилка"; +"modal_call_permission_request_title" = "Необхідний дозвіл"; +"modal_call_permission_request_explanation" = "Ви можете увімкнути дозвіл «Голосові та відеодзвінки» в налаштуваннях конфіденційності."; +"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Йой, сталася помилка"; +"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Будь ласка, спробуйте ще раз пізніше"; +"LOADING_CONVERSATIONS" = "Завантаження бесід..."; +"DATABASE_MIGRATION_FAILED" = "Сталася помилка під час оптимізації бази даних\n\nВи можете експортувати журнали програми, щоб мати можливість поділитися ними для усунення несправностей, або ви можете відновити свій пристрій\n\nПопередження: відновлення вашого пристрою призведе до втрати будь-яких даних, старіших за два тижні"; +"RECOVERY_PHASE_ERROR_GENERIC" = "Щось пішло не так. Будь ласка, перевірте вашу фразу відновлення і повторіть спробу."; +"RECOVERY_PHASE_ERROR_LENGTH" = "Схоже, ви не ввели достатню кількість слів. Будь ласка, перевірте вашу фразу відновлення і повторіть спробу."; +"RECOVERY_PHASE_ERROR_LAST_WORD" = "Здається, у вас немає останнього слова вашої фрази відновлення. Будь ласка, перевірте те, що ви ввели та повторіть спробу."; +"RECOVERY_PHASE_ERROR_INVALID_WORD" = "Схоже, що у вашій фразі відновлення є неправильне слово. Будь ласка, перевірте введене вами слово і спробуйте ще раз."; +"RECOVERY_PHASE_ERROR_FAILED" = "Не вдалося перевірити вашу фразу відновлення. Будь ласка, перевірте, що ви ввели і повторіть спробу."; +/* Indicates that an unknown error occurred while using Touch ID/Face ID/Phone Passcode. */ +"SCREEN_LOCK_ENABLE_UNKNOWN_ERROR" = "Немає доступу до автентифікації."; +/* Indicates that Touch ID/Face ID/Phone Passcode authentication failed. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_FAILED" = "Помилка автентифікації."; +/* Indicates that Touch ID/Face ID/Phone Passcode is 'locked out' on this device due to authentication failures. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_LOCKOUT" = "Занадто багато невдалих спроб автентифікації. Будь ласка, спробуйте ще раз пізніше."; +/* Indicates that Touch ID/Face ID/Phone Passcode are not available on this device. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE" = "Ви повинні увімкнути пароль в налаштуваннях iOS, щоб використовувати блокування екрана."; +/* Indicates that Touch ID/Face ID/Phone Passcode is not configured on this device. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_ENROLLED" = "Ви повинні увімкнути пароль в налаштуваннях iOS, щоб використовувати блокування екрана."; +/* Indicates that Touch ID/Face ID/Phone Passcode passcode is not set. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_PASSCODE_NOT_SET" = "Ви повинні увімкнути пароль в налаштуваннях iOS, щоб використовувати блокування екрана."; +/* Label for the button to send a message */ +"SEND_BUTTON_TITLE" = "Надіслати"; +/* Generic text for button that retries whatever the last action was. */ +"RETRY_BUTTON_TEXT" = "Спробувати знову"; +/* notification action */ +"SHOW_THREAD_BUTTON_TITLE" = "Показати чат"; +/* notification body */ +"SEND_FAILED_NOTIFICATION_BODY" = "Не вдалося відправити ваше повідомлення."; +"INVALID_SESSION_ID_MESSAGE" = "Будь ласка, перевірте Session ID або та спробуйте ще раз."; +"INVALID_RECOVERY_PHRASE_MESSAGE" = "Будь ласка, перевірте фразу відновлення і повторіть спробу."; +"MEDIA_TAB_TITLE" = "Медіафайли"; +"DOCUMENT_TAB_TITLE" = "Документи"; +"DOCUMENT_TILES_EMPTY_DOCUMENT" = "У вас немає жодного документа в цій розмові."; +"DOCUMENT_TILES_LOADING_MORE_RECENT_LABEL" = "Завантаження новішого документа…"; +"DOCUMENT_TILES_LOADING_OLDER_LABEL" = "Завантаження старішого документа…"; +/* The name for the emoji category 'Activities' */ +"EMOJI_CATEGORY_ACTIVITIES_NAME" = "Дії"; +/* The name for the emoji category 'Animals & Nature' */ +"EMOJI_CATEGORY_ANIMALS_NAME" = "Тварини та природа"; +/* The name for the emoji category 'Flags' */ +"EMOJI_CATEGORY_FLAGS_NAME" = "Прапорці"; +/* The name for the emoji category 'Food & Drink' */ +"EMOJI_CATEGORY_FOOD_NAME" = "Їжа та напої"; +/* The name for the emoji category 'Objects' */ +"EMOJI_CATEGORY_OBJECTS_NAME" = "Об'єкти"; +/* The name for the emoji category 'Recents' */ +"EMOJI_CATEGORY_RECENTS_NAME" = "Нещодавно використані"; +/* The name for the emoji category 'Smileys & People' */ +"EMOJI_CATEGORY_SMILEYSANDPEOPLE_NAME" = "Смайлики та люди"; +/* The name for the emoji category 'Symbols' */ +"EMOJI_CATEGORY_SYMBOLS_NAME" = "Символи"; +/* The name for the emoji category 'Travel & Places' */ +"EMOJI_CATEGORY_TRAVEL_NAME" = "Подорожі та місця"; +"EMOJI_REACTS_NOTIFICATION" = "%@ відреагував на повідомлення за допомогою %@."; +"EMOJI_REACTS_MORE_REACTORS_ONE" = "Та ще 1 користувач відреагував за допомогою %@ на це повідомлення."; +"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "Та ще %@ інших користувачів відреагували за допомогою %@ на це повідомлення."; +"EMOJI_REACTS_RATE_LIMIT_TOAST" = "Повільніше! Ви надіслали занадто багато реакцій емодзі. Спробуйте ще раз пізніше."; +/* New conversation screen*/ +"vc_new_conversation_title" = "Нова бесіда"; +"CREATE_GROUP_BUTTON_TITLE" = "Створити"; +"JOIN_COMMUNITY_BUTTON_TITLE" = "Приєднатися"; +"PRIVACY_TITLE" = "Конфіденційність"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Безпека екрана"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Заблокувати Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Вимагати Touch ID, Face ID або код доступу для розблокування Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Сповіщення про скриншот"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Отримувати сповіщення, коли контакт робить скриншот особистої бесіди."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Звіти про перегляд"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Звіти про перегляд"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Відправляти звіти про прочитання в особистих бесідах."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Індикатор набору тексту"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Індикатор набору тексту"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "Бачити та передавати індикатор про набір в особистих бесідах."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Попередній перегляд посилань"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Надсилати попередній перегляд посилань"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Активувати попередній перегляд для підтримуваних URL."; +"PRIVACY_SECTION_CALLS" = "Дзвінки (Beta)"; +"PRIVACY_CALLS_TITLE" = "Голосові та відеодзвінки"; +"PRIVACY_CALLS_DESCRIPTION" = "Вмикає можливість здійснювати голосові та відеодзвінки іншим користувачам."; +"PRIVACY_CALLS_WARNING_TITLE" = "Голосові та відеодзвінки (бета-версія)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Ваша IP-адреса є видимою співрозмовнику і серверу Oxen Foundation під час використання бета-дзвінків. Ви дійсно хочете увімкнути голосові та відеодзвінки?"; +"NOTIFICATIONS_TITLE" = "Сповіщення"; +"NOTIFICATIONS_SECTION_STRATEGY" = "Стратегія сповіщення"; +"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Використ. швидкий режим"; +"NOTIFICATIONS_STRATEGY_FAST_MODE_DESCRIPTION" = "Ви отримуватимете сповіщення про нове повідомлення надійно та одразу за допомогою серверів сповіщень Apple."; +"NOTIFICATIONS_STRATEGY_FAST_MODE_ACTION" = "Перейти до налаштувань сповіщень пристрою"; +"NOTIFICATIONS_SECTION_STYLE" = "Стиль сповіщень"; +"NOTIFICATIONS_STYLE_SOUND_TITLE" = "Звук"; +"NOTIFICATIONS_STYLE_SOUND_WHEN_OPEN_TITLE" = "Звук, коли додаток відкрито"; +"NOTIFICATIONS_STYLE_CONTENT_TITLE" = "Вміст сповіщення"; +"NOTIFICATIONS_STYLE_CONTENT_DESCRIPTION" = "Інформація, що показується в сповіщеннях."; +"NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT" = "Ім'я та вміст"; +"NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_ONLY" = "Тільки ім'я"; +"NOTIFICATIONS_STYLE_CONTENT_OPTION_NO_NAME_OR_CONTENT" = "Нічого"; +"CONVERSATION_SETTINGS_TITLE" = "Розмови"; +"CONVERSATION_SETTINGS_SECTION_MESSAGE_TRIMMING" = "Обрізка повідомлень"; +"CONVERSATION_SETTINGS_MESSAGE_TRIMMING_TITLE" = "Обрізати спільноти"; +"CONVERSATION_SETTINGS_MESSAGE_TRIMMING_DESCRIPTION" = "Видаляти повідомлення старші за 6 місяців зі спільнот, в яких налічується понад 2 000 повідомлень."; +"CONVERSATION_SETTINGS_SECTION_AUDIO_MESSAGES" = "Аудіоповідомлення"; +"CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_TITLE" = "Автовідтворення аудіоповідомлень"; +"CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_DESCRIPTION" = "Автовідтворення послідовних аудіоповідомлень."; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_TITLE" = "Заблоковані контакти"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_EMPTY_STATE" = "Ви не маєте заблокованих контактів."; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK" = "Розблокувати"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_SINGLE" = "Ви впевнені, що бажаєте розблокувати %@?"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_FALLBACK" = "цей контакт"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_1" = "Ви впевнені, що бажаєте розблокувати %@"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_2_SINGLE" = "та %@?"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_3" = "та ще %d інших?"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_ACTON" = "Розблокувати"; +"APPEARANCE_TITLE" = "Зовнішній вигляд"; +"APPEARANCE_THEMES_TITLE" = "Теми"; +"APPEARANCE_PRIMARY_COLOR_TITLE" = "Основний колір"; +"APPEARANCE_PRIMARY_COLOR_PREVIEW_INC_QUOTE" = "Як справи?"; +"APPEARANCE_PRIMARY_COLOR_PREVIEW_INC_MESSAGE" = "У мене все гаразд, дякую. А в тебе?"; +"APPEARANCE_PRIMARY_COLOR_PREVIEW_OUT_MESSAGE" = "У мене все чудово, дякую."; +"APPEARANCE_NIGHT_MODE_TITLE" = "Автоматичний нічний режим"; +"APPEARANCE_NIGHT_MODE_TOGGLE" = "Використовувати системні налаштування"; +"HELP_TITLE" = "Допомога"; +"HELP_REPORT_BUG_TITLE" = "Повідомити про помилку"; +"HELP_REPORT_BUG_DESCRIPTION" = "Експортуйте свої журнали, а потім завантажте файл через службу підтримки Session."; +"HELP_REPORT_BUG_ACTION_TITLE" = "Експортувати журнали"; +"HELP_TRANSLATE_TITLE" = "Перекласти Session"; +"HELP_FEEDBACK_TITLE" = "Будемо раді, якщо залишите нам відгук"; +"HELP_FAQ_TITLE" = "Часті питання"; +"HELP_SUPPORT_TITLE" = "Служба підтримки"; +"modal_clear_all_data_title" = "Очистити всі дані"; +"modal_clear_all_data_explanation" = "Це остаточно видалить ваші повідомлення та контакти. Ви хочете очистити лише цей пристрій, або також видалити свої дані з мережі?"; +"modal_clear_all_data_explanation_2" = "Ви впевнені, що хочете видалити дані з мережі? Якщо ви продовжите, то не зможете відновити ваші повідомлення або контакти."; +"modal_clear_all_data_device_only_button_title" = "Очистити лише пристрій"; +"modal_clear_all_data_entire_account_button_title" = "Очистити пристрій та мережу"; +"dialog_clear_all_data_deletion_failed_1" = "Дані не видалено одним сервісним вузлом. Ідентифікатор сервісного вузла: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Дані не видалено %@ сервісними вузлами. Ідентифікатори сервісних вузлів: %@."; +"modal_clear_all_data_confirm" = "Очистити"; +"modal_seed_title" = "Ваша фраза відновлення"; +"modal_seed_explanation" = "Ви можете використовувати вашу фразу відновлення для відновлення облікового запису або пов’язування з пристроєм."; +"modal_permission_explanation" = "Щоб продовжити, Session потребує доступу до %@. Ви можете дозволити доступ в параметрах iOS."; +"modal_permission_settings_title" = "Налаштування"; +"modal_permission_camera" = "камери"; +"modal_permission_microphone" = "мікрофона"; +"modal_permission_library" = "бібліотеки"; +"DISAPPEARING_MESSAGES_OFF" = "Вимк."; +"DISAPPEARING_MESSAGES_SUBTITLE_OFF" = "Вимк."; +"DISAPPEARING_MESSAGES_SUBTITLE_DISAPPEAR_AFTER" = "Зникнення після: %@"; +"COPY_GROUP_URL" = "Скопіювати URL групи"; +"NEW_CONVERSATION_CONTACTS_SECTION_TITLE" = "Контакти"; +"GROUP_ERROR_NO_MEMBER_SELECTION" = "Будь ласка, виберіть принаймні 1 учасника групи"; +"GROUP_CREATION_PLEASE_WAIT" = "Будь ласка, зачекайте поки створюється група..."; +"GROUP_CREATION_ERROR_TITLE" = "Не вдалося створити групу"; +"GROUP_CREATION_ERROR_MESSAGE" = "Будь ласка, перевірте підключення до Інтернету та спробуйте ще раз."; +"GROUP_UPDATE_ERROR_TITLE" = "Не вдалося оновити групу"; +"GROUP_UPDATE_ERROR_MESSAGE" = "Не можна залишити під час додавання або видалення інших учасників."; +"GROUP_ACTION_REMOVE" = "Видалити"; +"GROUP_TITLE_MEMBERS" = "Учасники"; +"GROUP_TITLE_FALLBACK" = "Група"; +"DM_ERROR_DIRECT_BLINDED_ID" = "Ви можете відправляти повідомлення користувачам з прихованими ID тільки в межах спільноти"; +"DM_ERROR_INVALID" = "Будь ласка, перевірте Session ID або ім'я ONS та спробуйте ще раз"; +"COMMUNITY_ERROR_INVALID_URL" = "Будь ласка, перевірте введену вами URL-адресу та спробуйте ще раз."; +"COMMUNITY_ERROR_GENERIC" = "Не вдалося приєднатися"; +"DISAPPERING_MESSAGES_TITLE" = "Зникаючі повідомлення"; +"DISAPPERING_MESSAGES_TYPE_TITLE" = "Видалити тип"; +"DISAPPERING_MESSAGES_TYPE_AFTER_READ_TITLE" = "Зникнення після прочитання"; +"DISAPPERING_MESSAGES_TYPE_AFTER_READ_DESCRIPTION" = "Повідомлення видаляються, після того, як вони були прочитані."; +"DISAPPERING_MESSAGES_TYPE_AFTER_SEND_TITLE" = "Зникнення після відправки"; +"DISAPPERING_MESSAGES_TYPE_AFTER_SEND_DESCRIPTION" = "Повідомлення видаляються, після того, як вони були відправлені."; +"DISAPPERING_MESSAGES_TIMER_TITLE" = "Таймер"; +"DISAPPERING_MESSAGES_SAVE_TITLE" = "Встановити"; +"DISAPPERING_MESSAGES_GROUP_WARNING" = "Цей параметр відноситься до всіх в цій розмові."; +"DISAPPERING_MESSAGES_GROUP_WARNING_ADMIN_ONLY" = "Це налаштування застосовується до всіх в цій розмові. Тільки адміністратори групи можуть змінити це налаштування."; +"DISAPPERING_MESSAGES_SUMMARY" = "Зникнення після %@ - %@"; +"DISAPPERING_MESSAGES_INFO_ENABLE" = "%@ встановив, що повідомлення зникають %@ після того, як вони були %@"; +"DISAPPERING_MESSAGES_INFO_UPDATE" = "%@ змінив налаштування таким чином, що повідомлення зникають %@ після того, як вони були %@"; +"DISAPPERING_MESSAGES_INFO_DISABLE" = "%@ вимкнув зникнення повідомлень"; +"MESSAGE_STATE_READ" = "Прочитано"; +"MESSAGE_STATE_SENT" = "Надіслано"; From 8cbd318cca6f457502f381fdc3339b760eb8954a Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 19 Sep 2023 10:27:49 +1000 Subject: [PATCH 04/12] Fixed an issue where the conversation screen was dismissed incorrectly Fixed a bug where going to the settings screen in a conversation with no messages would pop to the conversation list --- Session/Conversations/ConversationVC.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index e6e60e648..8cce3fe0a 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -569,6 +569,10 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers let threadId: String = viewModel.threadData.threadId if + ( + self.navigationController == nil || + self.navigationController?.viewControllers.contains(self) == false + ) && viewModel.threadData.threadIsNoteToSelf == false && viewModel.threadData.threadShouldBeVisible == false && !SessionUtil.conversationInConfig( From f92579db07617346f0c3f8b7534c6a3cf51ab29f Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 19 Sep 2023 15:40:58 +1000 Subject: [PATCH 05/12] Fixed a couple more bugs with link previews Fixed an issue where sending a link with a preview wouldn't work if you have a previous "failed" preview for the same link Fixed an issue where receiving a link with a preview could update all existing previews to an invalid state --- .../ConversationVC+Interaction.swift | 38 ++++++++++++++----- .../Database/Models/LinkPreview.swift | 6 +-- .../MessageReceiver+VisibleMessages.swift | 1 - 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 9d7c469a1..1b513ff95 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -532,16 +532,34 @@ extension ConversationVC: let insertedInteraction: Interaction = try optimisticData.interaction.inserted(db) self?.viewModel.associate(optimisticMessageId: optimisticData.id, to: insertedInteraction.id) - // If there is a LinkPreview and it doesn't match an existing one then add it now - if - let linkPreviewDraft: LinkPreviewDraft = optimisticData.linkPreviewDraft, - (try? insertedInteraction.linkPreview.isEmpty(db)) == true - { - try LinkPreview( - url: linkPreviewDraft.urlString, - title: linkPreviewDraft.title, - attachmentId: try optimisticData.linkPreviewAttachment?.inserted(db).id - ).insert(db) + // If there is a LinkPreview draft then check the state of any existing link previews and + // insert a new one if needed + if let linkPreviewDraft: LinkPreviewDraft = optimisticData.linkPreviewDraft { + let invalidLinkPreviewAttachmentStates: [Attachment.State] = [ + .failedDownload, .pendingDownload, .downloading, .failedUpload, .invalid + ] + let linkPreviewAttachmentId: String? = try? insertedInteraction.linkPreview + .select(.attachmentId) + .asRequest(of: String.self) + .fetchOne(db) + let linkPreviewAttachmentState: Attachment.State = linkPreviewAttachmentId + .map { + try? Attachment + .filter(id: $0) + .select(.state) + .asRequest(of: Attachment.State.self) + .fetchOne(db) + } + .defaulting(to: .invalid) + + // If we don't have a "valid" existing link preview then upsert a new one + if invalidLinkPreviewAttachmentStates.contains(linkPreviewAttachmentState) { + try LinkPreview( + url: linkPreviewDraft.urlString, + title: linkPreviewDraft.title, + attachmentId: try optimisticData.linkPreviewAttachment?.inserted(db).id + ).save(db) + } } // If there is a Quote the insert it now diff --git a/SessionMessagingKit/Database/Models/LinkPreview.swift b/SessionMessagingKit/Database/Models/LinkPreview.swift index b214bc78d..c87740015 100644 --- a/SessionMessagingKit/Database/Models/LinkPreview.swift +++ b/SessionMessagingKit/Database/Models/LinkPreview.swift @@ -77,7 +77,7 @@ public struct LinkPreview: Codable, Equatable, Hashable, FetchableRecord, Persis // MARK: - Protobuf public extension LinkPreview { - init?(_ db: Database, proto: SNProtoDataMessage, body: String?, sentTimestampMs: TimeInterval) throws { + init?(_ db: Database, proto: SNProtoDataMessage, sentTimestampMs: TimeInterval) throws { guard let previewProto = proto.preview.first else { throw LinkPreviewError.noPreview } guard URL(string: previewProto.url) != nil else { throw LinkPreviewError.invalidInput } guard LinkPreview.isValidLinkUrl(previewProto.url) else { throw LinkPreviewError.invalidInput } @@ -86,9 +86,7 @@ public extension LinkPreview { let timestamp: TimeInterval = LinkPreview.timestampFor(sentTimestampMs: sentTimestampMs) let maybeLinkPreview: LinkPreview? = try? LinkPreview .filter(LinkPreview.Columns.url == previewProto.url) - .filter(LinkPreview.Columns.timestamp == LinkPreview.timestampFor( - sentTimestampMs: Double(proto.timestamp) - )) + .filter(LinkPreview.Columns.timestamp == timestamp) .fetchOne(db) if let linkPreview: LinkPreview = maybeLinkPreview { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index 520002125..3f80ebe04 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -276,7 +276,6 @@ extension MessageReceiver { let linkPreview: LinkPreview? = try? LinkPreview( db, proto: dataMessage, - body: message.text, sentTimestampMs: (messageSentTimestamp * 1000) )?.saved(db) From 4c934d2fda2cf370d8ce4e9d0de5d227f629662f Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 20 Sep 2023 16:57:59 +1000 Subject: [PATCH 06/12] [WIP] Started work fixing XCode 15 build issues Reworked the EmojiGenerator to structure the 'EmojiWithSkinTones+String" file as the original structure was causing XCode 15 to build forever Reworked the seed node certificate loading in an effort to fix a crash Updated to the latest version of webp Commented out a line causing build issues Fixed a number of build warnings Fixed an issue which could cause migration issues when upgrading from certain old versions --- Podfile.lock | 21 +- Scripts/EmojiGenerator.swift | 190 +- Scripts/ProtoWrappers.py | 2 +- Session.xcodeproj/project.pbxproj | 2 + .../Content Views/QuoteView.swift | 1 - .../Content Views/VoiceMessageView.swift | 1 - Session/Emoji/Emoji+Category.swift | 66 +- Session/Emoji/Emoji+Name.swift | 2243 +-- Session/Emoji/Emoji+SkinTones.swift | 16 + Session/Emoji/Emoji.swift | 33 +- Session/Emoji/EmojiWithSkinTones+String.swift | 11979 ++++++---------- .../PhotoCaptureViewController.swift | 13 +- .../PushRegistrationManager.swift | 4 +- SessionMessagingKit/Calls/WebRTCSession.swift | 4 +- .../Database/Models/SessionThread.swift | 2 +- .../Protos/Generated/SNProto.swift | 60 +- .../Protos/Generated/WebSocketProto.swift | 12 +- .../MessageReceiver+VisibleMessages.swift | 4 +- .../Notification+MessageReceiver.swift | 1 + SessionUtilitiesKit/Networking/HTTP.swift | 43 +- 20 files changed, 6189 insertions(+), 8508 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index 4a101f497..3ca93e031 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -13,15 +13,18 @@ PODS: - DifferenceKit/Core - GRDB.swift/SQLCipher (6.13.0): - SQLCipher (>= 3.4.2) - - libwebp (1.2.1): - - libwebp/demux (= 1.2.1) - - libwebp/mux (= 1.2.1) - - libwebp/webp (= 1.2.1) - - libwebp/demux (1.2.1): + - libwebp (1.3.2): + - libwebp/demux (= 1.3.2) + - libwebp/mux (= 1.3.2) + - libwebp/sharpyuv (= 1.3.2) + - libwebp/webp (= 1.3.2) + - libwebp/demux (1.3.2): - libwebp/webp - - libwebp/mux (1.2.1): + - libwebp/mux (1.3.2): - libwebp/demux - - libwebp/webp (1.2.1) + - libwebp/sharpyuv (1.3.2) + - libwebp/webp (1.3.2): + - libwebp/sharpyuv - Nimble (10.0.0) - NVActivityIndicatorView (5.1.1): - NVActivityIndicatorView/Base (= 5.1.1) @@ -134,7 +137,6 @@ SPEC REPOS: - CocoaLumberjack - DifferenceKit - GRDB.swift - - libwebp - Nimble - NVActivityIndicatorView - OpenSSL-Universal @@ -146,6 +148,7 @@ SPEC REPOS: - SwiftProtobuf - WebRTC-lib trunk: + - libwebp - xcbeautify EXTERNAL SOURCES: @@ -186,7 +189,7 @@ SPEC CHECKSUMS: Curve25519Kit: e63f9859ede02438ae3defc5e1a87e09d1ec7ee6 DifferenceKit: ab185c4d7f9cef8af3fcf593e5b387fb81e999ca GRDB.swift: fe420b1af49ec519c7e96e07887ee44f5dfa2b78 - libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc + libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84 NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667 OpenSSL-Universal: e7311447fd2419f57420c79524b641537387eff2 diff --git a/Scripts/EmojiGenerator.swift b/Scripts/EmojiGenerator.swift index 44f906cc1..58e1cda55 100755 --- a/Scripts/EmojiGenerator.swift +++ b/Scripts/EmojiGenerator.swift @@ -263,26 +263,43 @@ extension EmojiGenerator { } } - static func writeStringConversionsFile(from emojiModel: EmojiModel) { - // Inline helpers: - var firstItem = true - func conditionalCheckForEmojiItem(_ item: EmojiModel.EmojiDefinition.Emoji) -> String { - let isFirst = (firstItem == true) - firstItem = false - - let prefix = isFirst ? "" : "} else " - let suffix = "if rawValue == \"\(item.emojiChar)\" {" - return prefix + suffix - } - func conversionForEmojiItem(_ item: EmojiModel.EmojiDefinition.Emoji, definition: EmojiModel.EmojiDefinition) -> String { - let skinToneString: String - if item.skintoneSequence.isEmpty { - skinToneString = "nil" - } else { - skinToneString = "[\(item.skintoneSequence.map { ".\($0)" }.joined(separator: ", "))]" + indirect enum Structure { + enum ChunkType { + case firstScalar + case scalarSum + + func chunk(_ character: Character, into size: UInt32) -> UInt32 { + guard size > 0 else { return 0 } + + let scalarValues: [UInt32] = character.unicodeScalars.map { $0.value } + + switch self { + case .firstScalar: return (scalarValues.first.map { $0 / size } ?? 0) + case .scalarSum: return (scalarValues.reduce(0, +) / size) + } + } + + func switchString(with variableName: String = "rawValue") -> String { + switch self { + case .firstScalar: return "rawValue.unicodeScalars.map({ $0.value }).first" + case .scalarSum: return "rawValue.unicodeScalars.map({ $0.value }).reduce(0, +)" + } } - return "self.init(baseEmoji: .\(definition.enumName), skinTones: \(skinToneString))" } + + case ifElse // XCode 15 taking over 10 min with M1 Pro (gave up) + case switchStatement // XCode 15 taking over 10 min with M1 Pro (gave up) + case directLookup // XCode 15 taking 93 sec with M1 Pro + case chunked(UInt32, Structure, ChunkType) // XCode 15 taking <10 sec with M1 Pro (chunk by 100) + } + typealias ChunkedEmojiInfo = ( + variant: EmojiModel.EmojiDefinition.Emoji, + baseName: String + ) + + static func writeStringConversionsFile(from emojiModel: EmojiModel) { + // This combination seems to have the smallest compile time (~2.2 sec out of all of the combinations) + let desiredStructure: Structure = .chunked(100, .directLookup, .scalarSum) // Conversion from String: Creates an initializer mapping a single character emoji string to an EmojiWithSkinTones // e.g. @@ -291,30 +308,131 @@ extension EmojiGenerator { writeBlock(fileName: "EmojiWithSkinTones+String.swift") { fileHandle in fileHandle.writeLine("extension EmojiWithSkinTones {") fileHandle.indent { - fileHandle.writeLine("init?(rawValue: String) {") - fileHandle.indent { - fileHandle.writeLine("guard rawValue.isSingleEmoji else { return nil }") - - emojiModel.definitions.forEach { definition in - definition.variants.forEach { emoji in - fileHandle.writeLine(conditionalCheckForEmojiItem(emoji)) - fileHandle.indent { - fileHandle.writeLine(conversionForEmojiItem(emoji, definition: definition)) + switch desiredStructure { + case .chunked(let chunkSize, let childStructure, let chunkType): + let chunkedEmojiInfo = emojiModel.definitions + .reduce(into: [UInt32: [ChunkedEmojiInfo]]()) { result, next in + next.variants.forEach { emoji in + let chunk: UInt32 = chunkType.chunk(emoji.emojiChar, into: chunkSize) + result[chunk] = ((result[chunk] ?? []) + [(emoji, next.enumName)]) + .sorted { lhs, rhs in lhs.variant < rhs.variant } + } } + .sorted { lhs, rhs in lhs.key < rhs.key } + + fileHandle.writeLine("init?(rawValue: String) {") + fileHandle.indent { + fileHandle.writeLine("guard rawValue.isSingleEmoji else { return nil }") + fileHandle.writeLine("switch \(chunkType.switchString()) {") + fileHandle.indent { + chunkedEmojiInfo.forEach { chunk, _ in + fileHandle.writeLine("case \(chunk): self = EmojiWithSkinTones.emojiFrom\(chunk)(rawValue)") + } + fileHandle.writeLine("default: self = EmojiWithSkinTones(unsupportedValue: rawValue)") + } + fileHandle.writeLine("}") } - } - - fileHandle.writeLine("} else {") - fileHandle.indent { - fileHandle.writeLine("self.init(unsupportedValue: rawValue)") - } - fileHandle.writeLine("}") + fileHandle.writeLine("}") + + chunkedEmojiInfo.forEach { chunk, emojiInfo in + fileHandle.writeLine("") + fileHandle.writeLine("private static func emojiFrom\(chunk)(_ rawValue: String) -> EmojiWithSkinTones {") + fileHandle.indent { + switch emojiInfo.count { + case 0: + fileHandle.writeLine("return EmojiWithSkinTones(unsupportedValue: rawValue)") + + default: + writeStructure( + childStructure, + for: emojiInfo, + using: fileHandle, + assignmentPrefix: "return " + ) + } + } + + fileHandle.writeLine("}") + } + + default: + fileHandle.writeLine("init?(rawValue: String) {") + fileHandle.indent { + fileHandle.writeLine("guard rawValue.isSingleEmoji else { return nil }") + writeStructure( + desiredStructure, + for: emojiModel.definitions + .flatMap { definition in + definition.variants.map { ($0, definition.enumName) } + }, + using: fileHandle + ) + } + fileHandle.writeLine("}") } - fileHandle.writeLine("}") } fileHandle.writeLine("}") } } + + private static func writeStructure( + _ structure: Structure, + for emojiInfo: [ChunkedEmojiInfo], + using fileHandle: WriteHandle, + assignmentPrefix: String = "self = " + ) { + func initItem(_ info: ChunkedEmojiInfo) -> String { + let skinToneString: String = { + guard !info.variant.skintoneSequence.isEmpty else { return "nil" } + return "[\(info.variant.skintoneSequence.map { ".\($0)" }.joined(separator: ", "))]" + }() + + return "EmojiWithSkinTones(baseEmoji: .\(info.baseName), skinTones: \(skinToneString))" + } + + switch structure { + case .ifElse: + emojiInfo.enumerated().forEach { index, info in + switch index { + case 0: fileHandle.writeLine("if rawValue == \"\(info.variant.emojiChar)\" {") + default: fileHandle.writeLine("} else if rawValue == \"\(info.variant.emojiChar)\" {") + } + + fileHandle.indent { + fileHandle.writeLine("\(assignmentPrefix)\(initItem(info))") + } + } + + fileHandle.writeLine("} else {") + fileHandle.indent { + fileHandle.writeLine("\(assignmentPrefix)EmojiWithSkinTones(unsupportedValue: rawValue)") + } + fileHandle.writeLine("}") + + case .switchStatement: + fileHandle.writeLine("switch rawValue {") + fileHandle.indent { + emojiInfo.forEach { info in + fileHandle.writeLine("case \"\(info.variant.emojiChar)\": \(assignmentPrefix)\(initItem(info))") + } + fileHandle.writeLine("default: \(assignmentPrefix)EmojiWithSkinTones(unsupportedValue: rawValue)") + } + fileHandle.writeLine("}") + + case .directLookup: + fileHandle.writeLine("let lookup: [String: EmojiWithSkinTones] = [") + fileHandle.indent { + emojiInfo.enumerated().forEach { index, info in + let isLast: Bool = (index == (emojiInfo.count - 1)) + fileHandle.writeLine("\"\(info.variant.emojiChar)\": \(initItem(info))\(isLast ? "" : ",")") + } + } + fileHandle.writeLine("]") + fileHandle.writeLine("\(assignmentPrefix)(lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue))") + + case .chunked: break // Provide one of the other types + } + } static func writeSkinToneLookupFile(from emojiModel: EmojiModel) { writeBlock(fileName: "Emoji+SkinTones.swift") { fileHandle in @@ -514,7 +632,7 @@ extension EmojiGenerator { fileHandle.indent { fileHandle.writeLine("switch self {") emojiModel.definitions.forEach { - fileHandle.writeLine("case .\($0.enumName): return \"\($0.shortNames.joined(separator:", "))\"") + fileHandle.writeLine("case .\($0.enumName): return \"\($0.shortNames.sorted().joined(separator:", "))\"") } fileHandle.writeLine("}") } diff --git a/Scripts/ProtoWrappers.py b/Scripts/ProtoWrappers.py index b346d1b1f..23b71451b 100755 --- a/Scripts/ProtoWrappers.py +++ b/Scripts/ProtoWrappers.py @@ -572,7 +572,7 @@ public func serializedData() throws -> Data { # if self.can_field_be_optional(field): writer.add('guard proto.%s else {' % field.has_accessor_name() ) writer.push_indent() - writer.add('throw %s.invalidProtobuf(description: "\(logTag) missing required field: %s")' % ( writer.invalid_protobuf_error_name, field.name_swift, ) ) + writer.add('throw %s.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: %s")' % ( writer.invalid_protobuf_error_name, field.name_swift, ) ) writer.pop_indent() writer.add('}') diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 48ee04b36..1d057de96 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -1924,6 +1924,7 @@ FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMessageRequest.swift; sourceTree = ""; }; FDC438CC27BC641200C60D73 /* Set+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Utilities.swift"; sourceTree = ""; }; FDC6D75F2862B3F600B04575 /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = ""; }; + FDCCC6E82ABA7402002BBEF5 /* EmojiGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiGenerator.swift; sourceTree = ""; }; FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGroupOnlyRequest.swift; sourceTree = ""; }; FDCDB8DD2810F73B00352A0C /* Differentiable+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Differentiable+Utilities.swift"; sourceTree = ""; }; FDCDB8DF2811007F00352A0C /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; @@ -4315,6 +4316,7 @@ isa = PBXGroup; children = ( FDE7214F287E50D50093DF33 /* ProtoWrappers.py */, + FDCCC6E82ABA7402002BBEF5 /* EmojiGenerator.swift */, FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */, FD5CE3442A3C5D96001A6DE3 /* DecryptExportedKey.swift */, ); diff --git a/Session/Conversations/Message Cells/Content Views/QuoteView.swift b/Session/Conversations/Message Cells/Content Views/QuoteView.swift index 141395d1d..176e13560 100644 --- a/Session/Conversations/Message Cells/Content Views/QuoteView.swift +++ b/Session/Conversations/Message Cells/Content Views/QuoteView.swift @@ -105,7 +105,6 @@ final class QuoteView: UIView { availableWidth -= cancelButtonSize } - let availableSpace = CGSize(width: availableWidth, height: .greatestFiniteMagnitude) var body: String? = quotedText // Main stack view diff --git a/Session/Conversations/Message Cells/Content Views/VoiceMessageView.swift b/Session/Conversations/Message Cells/Content Views/VoiceMessageView.swift index af2f386f8..5a3531a28 100644 --- a/Session/Conversations/Message Cells/Content Views/VoiceMessageView.swift +++ b/Session/Conversations/Message Cells/Content Views/VoiceMessageView.swift @@ -112,7 +112,6 @@ public final class VoiceMessageView: UIView { } private func setUpViewHierarchy() { - let toggleContainerSize = VoiceMessageView.toggleContainerSize let inset = VoiceMessageView.inset // Width & height diff --git a/Session/Emoji/Emoji+Category.swift b/Session/Emoji/Emoji+Category.swift index 3b6b8275b..3ef0a93d3 100644 --- a/Session/Emoji/Emoji+Category.swift +++ b/Session/Emoji/Emoji+Category.swift @@ -86,6 +86,7 @@ extension Emoji { .grimacing, .faceExhaling, .lyingFace, + .shakingFace, .relieved, .pensive, .sleepy, @@ -163,7 +164,6 @@ extension Emoji { .seeNoEvil, .hearNoEvil, .speakNoEvil, - .kiss, .loveLetter, .cupid, .giftHeart, @@ -178,14 +178,18 @@ extension Emoji { .heartOnFire, .mendingHeart, .heart, + .pinkHeart, .orangeHeart, .yellowHeart, .greenHeart, .blueHeart, + .lightBlueHeart, .purpleHeart, .brownHeart, .blackHeart, + .greyHeart, .whiteHeart, + .kiss, .oneHundred, .anger, .boom, @@ -193,7 +197,6 @@ extension Emoji { .sweatDrops, .dash, .hole, - .bomb, .speechBalloon, .eyeInSpeechBubble, .leftSpeechBubble, @@ -209,6 +212,8 @@ extension Emoji { .leftwardsHand, .palmDownHand, .palmUpHand, + .leftwardsPushingHand, + .rightwardsPushingHand, .okHand, .pinchedFingers, .pinchingHand, @@ -584,6 +589,8 @@ extension Emoji { .tiger2, .leopard, .horse, + .moose, + .donkey, .racehorse, .unicornFace, .zebraFace, @@ -646,6 +653,9 @@ extension Emoji { .flamingo, .peacock, .parrot, + .wing, + .blackBird, + .goose, .frog, .crocodile, .turtle, @@ -666,6 +676,7 @@ extension Emoji { .octopus, .shell, .coral, + .jellyfish, .snail, .butterfly, .bug, @@ -693,6 +704,7 @@ extension Emoji { .sunflower, .blossom, .tulip, + .hyacinth, .seedling, .pottedPlant, .evergreenTree, @@ -708,6 +720,7 @@ extension Emoji { .leaves, .emptyNest, .nestWithEggs, + .mushroom, ] case .food: return [ @@ -742,10 +755,11 @@ extension Emoji { .broccoli, .garlic, .onion, - .mushroom, .peanuts, .beans, .chestnut, + .gingerRoot, + .peaPod, .bread, .croissant, .baguetteBread, @@ -903,11 +917,10 @@ extension Emoji { .dart, .yoYo, .kite, + .gun, .eightBall, .crystalBall, .magicWand, - .nazarAmulet, - .hamsa, .videoGame, .joystick, .slotMachine, @@ -1176,6 +1189,7 @@ extension Emoji { .shorts, .bikini, .womansClothes, + .foldingHandFan, .purse, .handbag, .pouch, @@ -1190,6 +1204,7 @@ extension Emoji { .sandal, .balletShoes, .boot, + .hairPick, .crown, .womansHat, .tophat, @@ -1228,6 +1243,8 @@ extension Emoji { .banjo, .drumWithDrumsticks, .longDrum, + .maracas, + .flute, .iphone, .calling, .phone, @@ -1347,7 +1364,7 @@ extension Emoji { .hammerAndWrench, .daggerKnife, .crossedSwords, - .gun, + .bomb, .boomerang, .bowAndArrow, .shield, @@ -1408,6 +1425,8 @@ extension Emoji { .coffin, .headstone, .funeralUrn, + .nazarAmulet, + .hamsa, .moyai, .placard, .identificationCard, @@ -1473,6 +1492,7 @@ extension Emoji { .peaceSymbol, .menorahWithNineBranches, .sixPointedStar, + .khanda, .aries, .taurus, .gemini, @@ -1508,6 +1528,7 @@ extension Emoji { .lowBrightness, .highBrightness, .signalStrength, + .wireless, .vibrationMode, .mobilePhoneOff, .femaleSign, @@ -1962,6 +1983,7 @@ extension Emoji { case .grimacing: return .smileysAndPeople case .faceExhaling: return .smileysAndPeople case .lyingFace: return .smileysAndPeople + case .shakingFace: return .smileysAndPeople case .relieved: return .smileysAndPeople case .pensive: return .smileysAndPeople case .sleepy: return .smileysAndPeople @@ -2039,7 +2061,6 @@ extension Emoji { case .seeNoEvil: return .smileysAndPeople case .hearNoEvil: return .smileysAndPeople case .speakNoEvil: return .smileysAndPeople - case .kiss: return .smileysAndPeople case .loveLetter: return .smileysAndPeople case .cupid: return .smileysAndPeople case .giftHeart: return .smileysAndPeople @@ -2054,14 +2075,18 @@ extension Emoji { case .heartOnFire: return .smileysAndPeople case .mendingHeart: return .smileysAndPeople case .heart: return .smileysAndPeople + case .pinkHeart: return .smileysAndPeople case .orangeHeart: return .smileysAndPeople case .yellowHeart: return .smileysAndPeople case .greenHeart: return .smileysAndPeople case .blueHeart: return .smileysAndPeople + case .lightBlueHeart: return .smileysAndPeople case .purpleHeart: return .smileysAndPeople case .brownHeart: return .smileysAndPeople case .blackHeart: return .smileysAndPeople + case .greyHeart: return .smileysAndPeople case .whiteHeart: return .smileysAndPeople + case .kiss: return .smileysAndPeople case .oneHundred: return .smileysAndPeople case .anger: return .smileysAndPeople case .boom: return .smileysAndPeople @@ -2069,7 +2094,6 @@ extension Emoji { case .sweatDrops: return .smileysAndPeople case .dash: return .smileysAndPeople case .hole: return .smileysAndPeople - case .bomb: return .smileysAndPeople case .speechBalloon: return .smileysAndPeople case .eyeInSpeechBubble: return .smileysAndPeople case .leftSpeechBubble: return .smileysAndPeople @@ -2085,6 +2109,8 @@ extension Emoji { case .leftwardsHand: return .smileysAndPeople case .palmDownHand: return .smileysAndPeople case .palmUpHand: return .smileysAndPeople + case .leftwardsPushingHand: return .smileysAndPeople + case .rightwardsPushingHand: return .smileysAndPeople case .okHand: return .smileysAndPeople case .pinchedFingers: return .smileysAndPeople case .pinchingHand: return .smileysAndPeople @@ -2457,6 +2483,8 @@ extension Emoji { case .tiger2: return .animals case .leopard: return .animals case .horse: return .animals + case .moose: return .animals + case .donkey: return .animals case .racehorse: return .animals case .unicornFace: return .animals case .zebraFace: return .animals @@ -2519,6 +2547,9 @@ extension Emoji { case .flamingo: return .animals case .peacock: return .animals case .parrot: return .animals + case .wing: return .animals + case .blackBird: return .animals + case .goose: return .animals case .frog: return .animals case .crocodile: return .animals case .turtle: return .animals @@ -2539,6 +2570,7 @@ extension Emoji { case .octopus: return .animals case .shell: return .animals case .coral: return .animals + case .jellyfish: return .animals case .snail: return .animals case .butterfly: return .animals case .bug: return .animals @@ -2566,6 +2598,7 @@ extension Emoji { case .sunflower: return .animals case .blossom: return .animals case .tulip: return .animals + case .hyacinth: return .animals case .seedling: return .animals case .pottedPlant: return .animals case .evergreenTree: return .animals @@ -2581,6 +2614,7 @@ extension Emoji { case .leaves: return .animals case .emptyNest: return .animals case .nestWithEggs: return .animals + case .mushroom: return .animals case .grapes: return .food case .melon: return .food case .watermelon: return .food @@ -2612,10 +2646,11 @@ extension Emoji { case .broccoli: return .food case .garlic: return .food case .onion: return .food - case .mushroom: return .food case .peanuts: return .food case .beans: return .food case .chestnut: return .food + case .gingerRoot: return .food + case .peaPod: return .food case .bread: return .food case .croissant: return .food case .baguetteBread: return .food @@ -2988,11 +3023,10 @@ extension Emoji { case .dart: return .activities case .yoYo: return .activities case .kite: return .activities + case .gun: return .activities case .eightBall: return .activities case .crystalBall: return .activities case .magicWand: return .activities - case .nazarAmulet: return .activities - case .hamsa: return .activities case .videoGame: return .activities case .joystick: return .activities case .slotMachine: return .activities @@ -3037,6 +3071,7 @@ extension Emoji { case .shorts: return .objects case .bikini: return .objects case .womansClothes: return .objects + case .foldingHandFan: return .objects case .purse: return .objects case .handbag: return .objects case .pouch: return .objects @@ -3051,6 +3086,7 @@ extension Emoji { case .sandal: return .objects case .balletShoes: return .objects case .boot: return .objects + case .hairPick: return .objects case .crown: return .objects case .womansHat: return .objects case .tophat: return .objects @@ -3089,6 +3125,8 @@ extension Emoji { case .banjo: return .objects case .drumWithDrumsticks: return .objects case .longDrum: return .objects + case .maracas: return .objects + case .flute: return .objects case .iphone: return .objects case .calling: return .objects case .phone: return .objects @@ -3208,7 +3246,7 @@ extension Emoji { case .hammerAndWrench: return .objects case .daggerKnife: return .objects case .crossedSwords: return .objects - case .gun: return .objects + case .bomb: return .objects case .boomerang: return .objects case .bowAndArrow: return .objects case .shield: return .objects @@ -3269,6 +3307,8 @@ extension Emoji { case .coffin: return .objects case .headstone: return .objects case .funeralUrn: return .objects + case .nazarAmulet: return .objects + case .hamsa: return .objects case .moyai: return .objects case .placard: return .objects case .identificationCard: return .objects @@ -3331,6 +3371,7 @@ extension Emoji { case .peaceSymbol: return .symbols case .menorahWithNineBranches: return .symbols case .sixPointedStar: return .symbols + case .khanda: return .symbols case .aries: return .symbols case .taurus: return .symbols case .gemini: return .symbols @@ -3366,6 +3407,7 @@ extension Emoji { case .lowBrightness: return .symbols case .highBrightness: return .symbols case .signalStrength: return .symbols + case .wireless: return .symbols case .vibrationMode: return .symbols case .mobilePhoneOff: return .symbols case .femaleSign: return .symbols diff --git a/Session/Emoji/Emoji+Name.swift b/Session/Emoji/Emoji+Name.swift index 6acfa85e2..123bac051 100644 --- a/Session/Emoji/Emoji+Name.swift +++ b/Session/Emoji/Emoji+Name.swift @@ -8,213 +8,218 @@ extension Emoji { case .smiley: return "smiley, smiling face with open mouth" case .smile: return "smile, smiling face with open mouth and smiling eyes" case .grin: return "grin, grinning face with smiling eyes" - case .laughing: return "smiling face with open mouth and tightly-closed eyes, laughing, satisfied" - case .sweatSmile: return "sweatsmile, sweat_smile, smiling face with open mouth and cold sweat" - case .rollingOnTheFloorLaughing: return "rolling_on_the_floor_laughing, rollingonthefloorlaughing, rolling on the floor laughing" - case .joy: return "joy, face with tears of joy" - case .slightlySmilingFace: return "slightly smiling face, slightlysmilingface, slightly_smiling_face" - case .upsideDownFace: return "upsidedownface, upside_down_face, upside-down face" - case .meltingFace: return "meltingface, melting_face, melting face" + case .laughing: return "laughing, satisfied, smiling face with open mouth and tightly-closed eyes" + case .sweatSmile: return "smiling face with open mouth and cold sweat, sweat_smile, sweatsmile" + case .rollingOnTheFloorLaughing: return "rolling on the floor laughing, rolling_on_the_floor_laughing, rollingonthefloorlaughing" + case .joy: return "face with tears of joy, joy" + case .slightlySmilingFace: return "slightly smiling face, slightly_smiling_face, slightlysmilingface" + case .upsideDownFace: return "upside-down face, upside_down_face, upsidedownface" + case .meltingFace: return "melting face, melting_face, meltingface" case .wink: return "wink, winking face" case .blush: return "blush, smiling face with smiling eyes" case .innocent: return "innocent, smiling face with halo" case .smilingFaceWith3Hearts: return "smiling face with smiling eyes and three hearts, smiling_face_with_3_hearts, smilingfacewith3hearts" - case .heartEyes: return "smiling face with heart-shaped eyes, hearteyes, heart_eyes" - case .starStruck: return "starstruck, grinning_face_with_star_eyes, star-struck, grinning face with star eyes" - case .kissingHeart: return "kissing_heart, face throwing a kiss, kissingheart" - case .kissing: return "kissing face, kissing" - case .relaxed: return "white smiling face, relaxed" - case .kissingClosedEyes: return "kissing_closed_eyes, kissing face with closed eyes, kissingclosedeyes" + case .heartEyes: return "heart_eyes, hearteyes, smiling face with heart-shaped eyes" + case .starStruck: return "grinning face with star eyes, grinning_face_with_star_eyes, star-struck, starstruck" + case .kissingHeart: return "face throwing a kiss, kissing_heart, kissingheart" + case .kissing: return "kissing, kissing face" + case .relaxed: return "relaxed, white smiling face" + case .kissingClosedEyes: return "kissing face with closed eyes, kissing_closed_eyes, kissingclosedeyes" case .kissingSmilingEyes: return "kissing face with smiling eyes, kissing_smiling_eyes, kissingsmilingeyes" - case .smilingFaceWithTear: return "smilingfacewithtear, smiling face with tear, smiling_face_with_tear" - case .yum: return "yum, face savouring delicious food" - case .stuckOutTongue: return "stuck_out_tongue, face with stuck-out tongue, stuckouttongue" - case .stuckOutTongueWinkingEye: return "stuckouttonguewinkingeye, stuck_out_tongue_winking_eye, face with stuck-out tongue and winking eye" - case .zanyFace: return "grinning_face_with_one_large_and_one_small_eye, zany_face, grinning face with one large and one small eye, zanyface" - case .stuckOutTongueClosedEyes: return "stuckouttongueclosedeyes, stuck_out_tongue_closed_eyes, face with stuck-out tongue and tightly-closed eyes" - case .moneyMouthFace: return "moneymouthface, money_mouth_face, money-mouth face" - case .huggingFace: return "huggingface, hugging_face, hugging face" - case .faceWithHandOverMouth: return "face_with_hand_over_mouth, smiling face with smiling eyes and hand covering mouth, smiling_face_with_smiling_eyes_and_hand_covering_mouth, facewithhandovermouth" - case .faceWithOpenEyesAndHandOverMouth: return "face with open eyes and hand over mouth, facewithopeneyesandhandovermouth, face_with_open_eyes_and_hand_over_mouth" - case .faceWithPeekingEye: return "face_with_peeking_eye, face with peeking eye, facewithpeekingeye" - case .shushingFace: return "shushing_face, face_with_finger_covering_closed_lips, shushingface, face with finger covering closed lips" - case .thinkingFace: return "thinkingface, thinking face, thinking_face" - case .salutingFace: return "saluting_face, saluting face, salutingface" - case .zipperMouthFace: return "zippermouthface, zipper-mouth face, zipper_mouth_face" + case .smilingFaceWithTear: return "smiling face with tear, smiling_face_with_tear, smilingfacewithtear" + case .yum: return "face savouring delicious food, yum" + case .stuckOutTongue: return "face with stuck-out tongue, stuck_out_tongue, stuckouttongue" + case .stuckOutTongueWinkingEye: return "face with stuck-out tongue and winking eye, stuck_out_tongue_winking_eye, stuckouttonguewinkingeye" + case .zanyFace: return "grinning face with one large and one small eye, grinning_face_with_one_large_and_one_small_eye, zany_face, zanyface" + case .stuckOutTongueClosedEyes: return "face with stuck-out tongue and tightly-closed eyes, stuck_out_tongue_closed_eyes, stuckouttongueclosedeyes" + case .moneyMouthFace: return "money-mouth face, money_mouth_face, moneymouthface" + case .huggingFace: return "hugging face, hugging_face, huggingface" + case .faceWithHandOverMouth: return "face_with_hand_over_mouth, facewithhandovermouth, smiling face with smiling eyes and hand covering mouth, smiling_face_with_smiling_eyes_and_hand_covering_mouth" + case .faceWithOpenEyesAndHandOverMouth: return "face with open eyes and hand over mouth, face_with_open_eyes_and_hand_over_mouth, facewithopeneyesandhandovermouth" + case .faceWithPeekingEye: return "face with peeking eye, face_with_peeking_eye, facewithpeekingeye" + case .shushingFace: return "face with finger covering closed lips, face_with_finger_covering_closed_lips, shushing_face, shushingface" + case .thinkingFace: return "thinking face, thinking_face, thinkingface" + case .salutingFace: return "saluting face, saluting_face, salutingface" + case .zipperMouthFace: return "zipper-mouth face, zipper_mouth_face, zippermouthface" case .faceWithRaisedEyebrow: return "face with one eyebrow raised, face_with_one_eyebrow_raised, face_with_raised_eyebrow, facewithraisedeyebrow" - case .neutralFace: return "neutralface, neutral_face, neutral face" + case .neutralFace: return "neutral face, neutral_face, neutralface" case .expressionless: return "expressionless, expressionless face" - case .noMouth: return "no_mouth, face without mouth, nomouth" - case .dottedLineFace: return "dotted_line_face, dotted line face, dottedlineface" + case .noMouth: return "face without mouth, no_mouth, nomouth" + case .dottedLineFace: return "dotted line face, dotted_line_face, dottedlineface" case .faceInClouds: return "face in clouds, face_in_clouds, faceinclouds" case .smirk: return "smirk, smirking face" - case .unamused: return "unamused face, unamused" - case .faceWithRollingEyes: return "face_with_rolling_eyes, face with rolling eyes, facewithrollingeyes" + case .unamused: return "unamused, unamused face" + case .faceWithRollingEyes: return "face with rolling eyes, face_with_rolling_eyes, facewithrollingeyes" case .grimacing: return "grimacing, grimacing face" - case .faceExhaling: return "face_exhaling, face exhaling, faceexhaling" - case .lyingFace: return "lying_face, lying face, lyingface" + case .faceExhaling: return "face exhaling, face_exhaling, faceexhaling" + case .lyingFace: return "lying face, lying_face, lyingface" + case .shakingFace: return "shaking face, shaking_face, shakingface" case .relieved: return "relieved, relieved face" - case .pensive: return "pensive face, pensive" + case .pensive: return "pensive, pensive face" case .sleepy: return "sleepy, sleepy face" - case .droolingFace: return "drooling_face, drooling face, droolingface" + case .droolingFace: return "drooling face, drooling_face, droolingface" case .sleeping: return "sleeping, sleeping face" case .mask: return "face with medical mask, mask" case .faceWithThermometer: return "face with thermometer, face_with_thermometer, facewiththermometer" case .faceWithHeadBandage: return "face with head-bandage, face_with_head_bandage, facewithheadbandage" - case .nauseatedFace: return "nauseatedface, nauseated face, nauseated_face" - case .faceVomiting: return "face_with_open_mouth_vomiting, face_vomiting, facevomiting, face with open mouth vomiting" - case .sneezingFace: return "sneezingface, sneezing face, sneezing_face" - case .hotFace: return "hot_face, overheated face, hotface" - case .coldFace: return "freezing face, cold_face, coldface" - case .woozyFace: return "woozy_face, face with uneven eyes and wavy mouth, woozyface" - case .dizzyFace: return "dizzy_face, dizzy face, dizzyface" - case .faceWithSpiralEyes: return "face with spiral eyes, facewithspiraleyes, face_with_spiral_eyes" - case .explodingHead: return "shocked_face_with_exploding_head, explodinghead, shocked face with exploding head, exploding_head" - case .faceWithCowboyHat: return "face_with_cowboy_hat, face with cowboy hat, facewithcowboyhat" - case .partyingFace: return "partyingface, partying_face, face with party horn and party hat" - case .disguisedFace: return "disguised_face, disguisedface, disguised face" - case .sunglasses: return "sunglasses, smiling face with sunglasses" + case .nauseatedFace: return "nauseated face, nauseated_face, nauseatedface" + case .faceVomiting: return "face with open mouth vomiting, face_vomiting, face_with_open_mouth_vomiting, facevomiting" + case .sneezingFace: return "sneezing face, sneezing_face, sneezingface" + case .hotFace: return "hot_face, hotface, overheated face" + case .coldFace: return "cold_face, coldface, freezing face" + case .woozyFace: return "face with uneven eyes and wavy mouth, woozy_face, woozyface" + case .dizzyFace: return "dizzy face, dizzy_face, dizzyface" + case .faceWithSpiralEyes: return "face with spiral eyes, face_with_spiral_eyes, facewithspiraleyes" + case .explodingHead: return "exploding_head, explodinghead, shocked face with exploding head, shocked_face_with_exploding_head" + case .faceWithCowboyHat: return "face with cowboy hat, face_with_cowboy_hat, facewithcowboyhat" + case .partyingFace: return "face with party horn and party hat, partying_face, partyingface" + case .disguisedFace: return "disguised face, disguised_face, disguisedface" + case .sunglasses: return "smiling face with sunglasses, sunglasses" case .nerdFace: return "nerd face, nerd_face, nerdface" - case .faceWithMonocle: return "face_with_monocle, face with monocle, facewithmonocle" - case .confused: return "confused face, confused" - case .faceWithDiagonalMouth: return "face with diagonal mouth, facewithdiagonalmouth, face_with_diagonal_mouth" + case .faceWithMonocle: return "face with monocle, face_with_monocle, facewithmonocle" + case .confused: return "confused, confused face" + case .faceWithDiagonalMouth: return "face with diagonal mouth, face_with_diagonal_mouth, facewithdiagonalmouth" case .worried: return "worried, worried face" - case .slightlyFrowningFace: return "slightly_frowning_face, slightlyfrowningface, slightly frowning face" - case .whiteFrowningFace: return "whitefrowningface, white_frowning_face, frowning face" + case .slightlyFrowningFace: return "slightly frowning face, slightly_frowning_face, slightlyfrowningface" + case .whiteFrowningFace: return "frowning face, white_frowning_face, whitefrowningface" case .openMouth: return "face with open mouth, open_mouth, openmouth" - case .hushed: return "hushed face, hushed" - case .astonished: return "astonished face, astonished" + case .hushed: return "hushed, hushed face" + case .astonished: return "astonished, astonished face" case .flushed: return "flushed, flushed face" case .pleadingFace: return "face with pleading eyes, pleading_face, pleadingface" - case .faceHoldingBackTears: return "faceholdingbacktears, face_holding_back_tears, face holding back tears" - case .frowning: return "frowning face with open mouth, frowning" + case .faceHoldingBackTears: return "face holding back tears, face_holding_back_tears, faceholdingbacktears" + case .frowning: return "frowning, frowning face with open mouth" case .anguished: return "anguished, anguished face" case .fearful: return "fearful, fearful face" - case .coldSweat: return "coldsweat, cold_sweat, face with open mouth and cold sweat" - case .disappointedRelieved: return "disappointed but relieved face, disappointedrelieved, disappointed_relieved" - case .cry: return "crying face, cry" - case .sob: return "sob, loudly crying face" + case .coldSweat: return "cold_sweat, coldsweat, face with open mouth and cold sweat" + case .disappointedRelieved: return "disappointed but relieved face, disappointed_relieved, disappointedrelieved" + case .cry: return "cry, crying face" + case .sob: return "loudly crying face, sob" case .scream: return "face screaming in fear, scream" case .confounded: return "confounded, confounded face" case .persevere: return "persevere, persevering face" case .disappointed: return "disappointed, disappointed face" - case .sweat: return "sweat, face with cold sweat" + case .sweat: return "face with cold sweat, sweat" case .weary: return "weary, weary face" case .tiredFace: return "tired face, tired_face, tiredface" - case .yawningFace: return "yawningface, yawning face, yawning_face" + case .yawningFace: return "yawning face, yawning_face, yawningface" case .triumph: return "face with look of triumph, triumph" - case .rage: return "rage, pouting face" + case .rage: return "pouting face, rage" case .angry: return "angry, angry face" - case .faceWithSymbolsOnMouth: return "serious_face_with_symbols_covering_mouth, serious face with symbols covering mouth, face_with_symbols_on_mouth, facewithsymbolsonmouth" - case .smilingImp: return "smilingimp, smiling face with horns, smiling_imp" + case .faceWithSymbolsOnMouth: return "face_with_symbols_on_mouth, facewithsymbolsonmouth, serious face with symbols covering mouth, serious_face_with_symbols_covering_mouth" + case .smilingImp: return "smiling face with horns, smiling_imp, smilingimp" case .imp: return "imp" case .skull: return "skull" case .skullAndCrossbones: return "skull and crossbones, skull_and_crossbones, skullandcrossbones" - case .hankey: return "pile of poo, shit, poop, hankey" + case .hankey: return "hankey, pile of poo, poop, shit" case .clownFace: return "clown face, clown_face, clownface" - case .japaneseOgre: return "japanese_ogre, japaneseogre, japanese ogre" + case .japaneseOgre: return "japanese ogre, japanese_ogre, japaneseogre" case .japaneseGoblin: return "japanese goblin, japanese_goblin, japanesegoblin" case .ghost: return "ghost" case .alien: return "alien, extraterrestrial alien" - case .spaceInvader: return "spaceinvader, alien monster, space_invader" - case .robotFace: return "robot_face, robot face, robotface" + case .spaceInvader: return "alien monster, space_invader, spaceinvader" + case .robotFace: return "robot face, robot_face, robotface" case .smileyCat: return "smiley_cat, smileycat, smiling cat face with open mouth" - case .smileCat: return "smilecat, grinning cat face with smiling eyes, smile_cat" - case .joyCat: return "joy_cat, joycat, cat face with tears of joy" - case .heartEyesCat: return "heart_eyes_cat, smiling cat face with heart-shaped eyes, hearteyescat" - case .smirkCat: return "smirk_cat, cat face with wry smile, smirkcat" + case .smileCat: return "grinning cat face with smiling eyes, smile_cat, smilecat" + case .joyCat: return "cat face with tears of joy, joy_cat, joycat" + case .heartEyesCat: return "heart_eyes_cat, hearteyescat, smiling cat face with heart-shaped eyes" + case .smirkCat: return "cat face with wry smile, smirk_cat, smirkcat" case .kissingCat: return "kissing cat face with closed eyes, kissing_cat, kissingcat" - case .screamCat: return "scream_cat, weary cat face, screamcat" + case .screamCat: return "scream_cat, screamcat, weary cat face" case .cryingCatFace: return "crying cat face, crying_cat_face, cryingcatface" - case .poutingCat: return "pouting_cat, poutingcat, pouting cat face" - case .seeNoEvil: return "see_no_evil, see-no-evil monkey, seenoevil" - case .hearNoEvil: return "hearnoevil, hear-no-evil monkey, hear_no_evil" - case .speakNoEvil: return "speak_no_evil, speaknoevil, speak-no-evil monkey" - case .kiss: return "kiss mark, kiss" - case .loveLetter: return "love letter, loveletter, love_letter" - case .cupid: return "heart with arrow, cupid" - case .giftHeart: return "heart with ribbon, gift_heart, giftheart" - case .sparklingHeart: return "sparklingheart, sparkling_heart, sparkling heart" + case .poutingCat: return "pouting cat face, pouting_cat, poutingcat" + case .seeNoEvil: return "see-no-evil monkey, see_no_evil, seenoevil" + case .hearNoEvil: return "hear-no-evil monkey, hear_no_evil, hearnoevil" + case .speakNoEvil: return "speak-no-evil monkey, speak_no_evil, speaknoevil" + case .loveLetter: return "love letter, love_letter, loveletter" + case .cupid: return "cupid, heart with arrow" + case .giftHeart: return "gift_heart, giftheart, heart with ribbon" + case .sparklingHeart: return "sparkling heart, sparkling_heart, sparklingheart" case .heartpulse: return "growing heart, heartpulse" - case .heartbeat: return "heartbeat, beating heart" - case .revolvingHearts: return "revolving hearts, revolvinghearts, revolving_hearts" - case .twoHearts: return "two hearts, twohearts, two_hearts" - case .heartDecoration: return "heart_decoration, heart decoration, heartdecoration" - case .heavyHeartExclamationMarkOrnament: return "heavy_heart_exclamation_mark_ornament, heart exclamation, heavyheartexclamationmarkornament" - case .brokenHeart: return "brokenheart, broken heart, broken_heart" - case .heartOnFire: return "heartonfire, heart on fire, heart_on_fire" - case .mendingHeart: return "mending_heart, mending heart, mendingheart" - case .heart: return "heavy black heart, heart" - case .orangeHeart: return "orange_heart, orangeheart, orange heart" - case .yellowHeart: return "yellow_heart, yellow heart, yellowheart" - case .greenHeart: return "greenheart, green heart, green_heart" - case .blueHeart: return "blue heart, blueheart, blue_heart" - case .purpleHeart: return "purpleheart, purple_heart, purple heart" - case .brownHeart: return "brown_heart, brownheart, brown heart" - case .blackHeart: return "blackheart, black_heart, black heart" - case .whiteHeart: return "white heart, whiteheart, white_heart" - case .oneHundred: return "hundred points symbol, 100, onehundred" - case .anger: return "anger symbol, anger" - case .boom: return "collision, boom, collision symbol" + case .heartbeat: return "beating heart, heartbeat" + case .revolvingHearts: return "revolving hearts, revolving_hearts, revolvinghearts" + case .twoHearts: return "two hearts, two_hearts, twohearts" + case .heartDecoration: return "heart decoration, heart_decoration, heartdecoration" + case .heavyHeartExclamationMarkOrnament: return "heart exclamation, heavy_heart_exclamation_mark_ornament, heavyheartexclamationmarkornament" + case .brokenHeart: return "broken heart, broken_heart, brokenheart" + case .heartOnFire: return "heart on fire, heart_on_fire, heartonfire" + case .mendingHeart: return "mending heart, mending_heart, mendingheart" + case .heart: return "heart, heavy black heart" + case .pinkHeart: return "pink heart, pink_heart, pinkheart" + case .orangeHeart: return "orange heart, orange_heart, orangeheart" + case .yellowHeart: return "yellow heart, yellow_heart, yellowheart" + case .greenHeart: return "green heart, green_heart, greenheart" + case .blueHeart: return "blue heart, blue_heart, blueheart" + case .lightBlueHeart: return "light blue heart, light_blue_heart, lightblueheart" + case .purpleHeart: return "purple heart, purple_heart, purpleheart" + case .brownHeart: return "brown heart, brown_heart, brownheart" + case .blackHeart: return "black heart, black_heart, blackheart" + case .greyHeart: return "grey heart, grey_heart, greyheart" + case .whiteHeart: return "white heart, white_heart, whiteheart" + case .kiss: return "kiss, kiss mark" + case .oneHundred: return "100, hundred points symbol, onehundred" + case .anger: return "anger, anger symbol" + case .boom: return "boom, collision, collision symbol" case .dizzy: return "dizzy, dizzy symbol" - case .sweatDrops: return "splashing sweat symbol, sweatdrops, sweat_drops" - case .dash: return "dash symbol, dash" + case .sweatDrops: return "splashing sweat symbol, sweat_drops, sweatdrops" + case .dash: return "dash, dash symbol" case .hole: return "hole" - case .bomb: return "bomb" - case .speechBalloon: return "speech_balloon, speech balloon, speechballoon" - case .eyeInSpeechBubble: return "eyeinspeechbubble, eye-in-speech-bubble, eye in speech bubble" - case .leftSpeechBubble: return "left_speech_bubble, left speech bubble, leftspeechbubble" - case .rightAngerBubble: return "right anger bubble, rightangerbubble, right_anger_bubble" - case .thoughtBalloon: return "thought_balloon, thoughtballoon, thought balloon" - case .zzz: return "zzz, sleeping symbol" - case .wave: return "waving hand sign, wave" - case .raisedBackOfHand: return "raised_back_of_hand, raised back of hand, raisedbackofhand" - case .raisedHandWithFingersSplayed: return "raisedhandwithfingerssplayed, hand with fingers splayed, raised_hand_with_fingers_splayed" - case .hand: return "raised_hand, raised hand, hand" - case .spockHand: return "raised hand with part between middle and ring fingers, spockhand, spock-hand" - case .rightwardsHand: return "rightwardshand, rightwards hand, rightwards_hand" - case .leftwardsHand: return "leftwards hand, leftwardshand, leftwards_hand" - case .palmDownHand: return "palmdownhand, palm_down_hand, palm down hand" - case .palmUpHand: return "palmuphand, palm_up_hand, palm up hand" - case .okHand: return "ok hand sign, okhand, ok_hand" - case .pinchedFingers: return "pinched_fingers, pinchedfingers, pinched fingers" - case .pinchingHand: return "pinching hand, pinchinghand, pinching_hand" + case .speechBalloon: return "speech balloon, speech_balloon, speechballoon" + case .eyeInSpeechBubble: return "eye in speech bubble, eye-in-speech-bubble, eyeinspeechbubble" + case .leftSpeechBubble: return "left speech bubble, left_speech_bubble, leftspeechbubble" + case .rightAngerBubble: return "right anger bubble, right_anger_bubble, rightangerbubble" + case .thoughtBalloon: return "thought balloon, thought_balloon, thoughtballoon" + case .zzz: return "sleeping symbol, zzz" + case .wave: return "wave, waving hand sign" + case .raisedBackOfHand: return "raised back of hand, raised_back_of_hand, raisedbackofhand" + case .raisedHandWithFingersSplayed: return "hand with fingers splayed, raised_hand_with_fingers_splayed, raisedhandwithfingerssplayed" + case .hand: return "hand, raised hand, raised_hand" + case .spockHand: return "raised hand with part between middle and ring fingers, spock-hand, spockhand" + case .rightwardsHand: return "rightwards hand, rightwards_hand, rightwardshand" + case .leftwardsHand: return "leftwards hand, leftwards_hand, leftwardshand" + case .palmDownHand: return "palm down hand, palm_down_hand, palmdownhand" + case .palmUpHand: return "palm up hand, palm_up_hand, palmuphand" + case .leftwardsPushingHand: return "leftwards pushing hand, leftwards_pushing_hand, leftwardspushinghand" + case .rightwardsPushingHand: return "rightwards pushing hand, rightwards_pushing_hand, rightwardspushinghand" + case .okHand: return "ok hand sign, ok_hand, okhand" + case .pinchedFingers: return "pinched fingers, pinched_fingers, pinchedfingers" + case .pinchingHand: return "pinching hand, pinching_hand, pinchinghand" case .v: return "v, victory hand" - case .crossedFingers: return "crossedfingers, hand_with_index_and_middle_fingers_crossed, hand with index and middle fingers crossed, crossed_fingers" - case .handWithIndexFingerAndThumbCrossed: return "hand_with_index_finger_and_thumb_crossed, handwithindexfingerandthumbcrossed, hand with index finger and thumb crossed" - case .iLoveYouHandSign: return "i_love_you_hand_sign, iloveyouhandsign, i love you hand sign" - case .theHorns: return "sign_of_the_horns, the_horns, thehorns, sign of the horns" - case .callMeHand: return "callmehand, call_me_hand, call me hand" - case .pointLeft: return "pointleft, point_left, white left pointing backhand index" - case .pointRight: return "white right pointing backhand index, point_right, pointright" - case .pointUp2: return "point_up_2, white up pointing backhand index, pointup2" - case .middleFinger: return "middle_finger, reversed_hand_with_middle_finger_extended, middlefinger, reversed hand with middle finger extended" - case .pointDown: return "point_down, white down pointing backhand index, pointdown" - case .pointUp: return "point_up, white up pointing index, pointup" - case .indexPointingAtTheViewer: return "index_pointing_at_the_viewer, indexpointingattheviewer, index pointing at the viewer" - case .plusOne: return "+1, thumbsup, thumbs up sign, plusone" - case .negativeOne: return "thumbsdown, -1, thumbs down sign, negativeone" + case .crossedFingers: return "crossed_fingers, crossedfingers, hand with index and middle fingers crossed, hand_with_index_and_middle_fingers_crossed" + case .handWithIndexFingerAndThumbCrossed: return "hand with index finger and thumb crossed, hand_with_index_finger_and_thumb_crossed, handwithindexfingerandthumbcrossed" + case .iLoveYouHandSign: return "i love you hand sign, i_love_you_hand_sign, iloveyouhandsign" + case .theHorns: return "sign of the horns, sign_of_the_horns, the_horns, thehorns" + case .callMeHand: return "call me hand, call_me_hand, callmehand" + case .pointLeft: return "point_left, pointleft, white left pointing backhand index" + case .pointRight: return "point_right, pointright, white right pointing backhand index" + case .pointUp2: return "point_up_2, pointup2, white up pointing backhand index" + case .middleFinger: return "middle_finger, middlefinger, reversed hand with middle finger extended, reversed_hand_with_middle_finger_extended" + case .pointDown: return "point_down, pointdown, white down pointing backhand index" + case .pointUp: return "point_up, pointup, white up pointing index" + case .indexPointingAtTheViewer: return "index pointing at the viewer, index_pointing_at_the_viewer, indexpointingattheviewer" + case .plusOne: return "+1, plusone, thumbs up sign, thumbsup" + case .negativeOne: return "-1, negativeone, thumbs down sign, thumbsdown" case .fist: return "fist, raised fist" - case .facepunch: return "punch, facepunch, fisted hand sign" - case .leftFacingFist: return "left-facing_fist, left-facing fist, leftfacingfist" - case .rightFacingFist: return "right-facing fist, rightfacingfist, right-facing_fist" - case .clap: return "clapping hands sign, clap" + case .facepunch: return "facepunch, fisted hand sign, punch" + case .leftFacingFist: return "left-facing fist, left-facing_fist, leftfacingfist" + case .rightFacingFist: return "right-facing fist, right-facing_fist, rightfacingfist" + case .clap: return "clap, clapping hands sign" case .raisedHands: return "person raising both hands in celebration, raised_hands, raisedhands" - case .heartHands: return "heart_hands, hearthands, heart hands" + case .heartHands: return "heart hands, heart_hands, hearthands" case .openHands: return "open hands sign, open_hands, openhands" case .palmsUpTogether: return "palms up together, palms_up_together, palmsuptogether" case .handshake: return "handshake" - case .pray: return "pray, person with folded hands" + case .pray: return "person with folded hands, pray" case .writingHand: return "writing hand, writing_hand, writinghand" - case .nailCare: return "nailcare, nail polish, nail_care" + case .nailCare: return "nail polish, nail_care, nailcare" case .selfie: return "selfie" - case .muscle: return "muscle, flexed biceps" - case .mechanicalArm: return "mechanicalarm, mechanical_arm, mechanical arm" - case .mechanicalLeg: return "mechanical leg, mechanicalleg, mechanical_leg" + case .muscle: return "flexed biceps, muscle" + case .mechanicalArm: return "mechanical arm, mechanical_arm, mechanicalarm" + case .mechanicalLeg: return "mechanical leg, mechanical_leg, mechanicalleg" case .leg: return "leg" case .foot: return "foot" case .ear: return "ear" - case .earWithHearingAid: return "earwithhearingaid, ear with hearing aid, ear_with_hearing_aid" + case .earWithHearingAid: return "ear with hearing aid, ear_with_hearing_aid, earwithhearingaid" case .nose: return "nose" case .brain: return "brain" case .anatomicalHeart: return "anatomical heart, anatomical_heart, anatomicalheart" @@ -224,352 +229,354 @@ extension Emoji { case .eyes: return "eyes" case .eye: return "eye" case .tongue: return "tongue" - case .lips: return "mouth, lips" - case .bitingLip: return "biting lip, bitinglip, biting_lip" + case .lips: return "lips, mouth" + case .bitingLip: return "biting lip, biting_lip, bitinglip" case .baby: return "baby" case .child: return "child" case .boy: return "boy" case .girl: return "girl" case .adult: return "adult" - case .personWithBlondHair: return "person with blond hair, personwithblondhair, person_with_blond_hair" + case .personWithBlondHair: return "person with blond hair, person_with_blond_hair, personwithblondhair" case .man: return "man" - case .beardedPerson: return "bearded_person, beardedperson, bearded person" - case .manWithBeard: return "man: beard, manwithbeard, man_with_beard" - case .womanWithBeard: return "womanwithbeard, woman_with_beard, woman: beard" - case .redHairedMan: return "man: red hair, redhairedman, red_haired_man" - case .curlyHairedMan: return "curlyhairedman, curly_haired_man, man: curly hair" - case .whiteHairedMan: return "white_haired_man, man: white hair, whitehairedman" - case .baldMan: return "baldman, man: bald, bald_man" + case .beardedPerson: return "bearded person, bearded_person, beardedperson" + case .manWithBeard: return "man: beard, man_with_beard, manwithbeard" + case .womanWithBeard: return "woman: beard, woman_with_beard, womanwithbeard" + case .redHairedMan: return "man: red hair, red_haired_man, redhairedman" + case .curlyHairedMan: return "curly_haired_man, curlyhairedman, man: curly hair" + case .whiteHairedMan: return "man: white hair, white_haired_man, whitehairedman" + case .baldMan: return "bald_man, baldman, man: bald" case .woman: return "woman" - case .redHairedWoman: return "redhairedwoman, red_haired_woman, woman: red hair" - case .redHairedPerson: return "redhairedperson, red_haired_person, person: red hair" - case .curlyHairedWoman: return "curlyhairedwoman, curly_haired_woman, woman: curly hair" - case .curlyHairedPerson: return "curlyhairedperson, person: curly hair, curly_haired_person" - case .whiteHairedWoman: return "white_haired_woman, woman: white hair, whitehairedwoman" - case .whiteHairedPerson: return "whitehairedperson, white_haired_person, person: white hair" - case .baldWoman: return "woman: bald, bald_woman, baldwoman" - case .baldPerson: return "bald_person, person: bald, baldperson" - case .blondHairedWoman: return "woman: blond hair, blondhairedwoman, blond-haired-woman" + case .redHairedWoman: return "red_haired_woman, redhairedwoman, woman: red hair" + case .redHairedPerson: return "person: red hair, red_haired_person, redhairedperson" + case .curlyHairedWoman: return "curly_haired_woman, curlyhairedwoman, woman: curly hair" + case .curlyHairedPerson: return "curly_haired_person, curlyhairedperson, person: curly hair" + case .whiteHairedWoman: return "white_haired_woman, whitehairedwoman, woman: white hair" + case .whiteHairedPerson: return "person: white hair, white_haired_person, whitehairedperson" + case .baldWoman: return "bald_woman, baldwoman, woman: bald" + case .baldPerson: return "bald_person, baldperson, person: bald" + case .blondHairedWoman: return "blond-haired-woman, blondhairedwoman, woman: blond hair" case .blondHairedMan: return "blond-haired-man, blondhairedman, man: blond hair" - case .olderAdult: return "older_adult, older adult, olderadult" - case .olderMan: return "older_man, older man, olderman" + case .olderAdult: return "older adult, older_adult, olderadult" + case .olderMan: return "older man, older_man, olderman" case .olderWoman: return "older woman, older_woman, olderwoman" - case .personFrowning: return "person_frowning, personfrowning, person frowning" - case .manFrowning: return "man frowning, manfrowning, man-frowning" + case .personFrowning: return "person frowning, person_frowning, personfrowning" + case .manFrowning: return "man frowning, man-frowning, manfrowning" case .womanFrowning: return "woman frowning, woman-frowning, womanfrowning" - case .personWithPoutingFace: return "person with pouting face, personwithpoutingface, person_with_pouting_face" + case .personWithPoutingFace: return "person with pouting face, person_with_pouting_face, personwithpoutingface" case .manPouting: return "man pouting, man-pouting, manpouting" - case .womanPouting: return "woman-pouting, woman pouting, womanpouting" - case .noGood: return "no_good, nogood, face with no good gesture" - case .manGesturingNo: return "mangesturingno, man-gesturing-no, man gesturing no" - case .womanGesturingNo: return "woman gesturing no, womangesturingno, woman-gesturing-no" - case .okWoman: return "ok_woman, okwoman, face with ok gesture" + case .womanPouting: return "woman pouting, woman-pouting, womanpouting" + case .noGood: return "face with no good gesture, no_good, nogood" + case .manGesturingNo: return "man gesturing no, man-gesturing-no, mangesturingno" + case .womanGesturingNo: return "woman gesturing no, woman-gesturing-no, womangesturingno" + case .okWoman: return "face with ok gesture, ok_woman, okwoman" case .manGesturingOk: return "man gesturing ok, man-gesturing-ok, mangesturingok" - case .womanGesturingOk: return "woman-gesturing-ok, woman gesturing ok, womangesturingok" - case .informationDeskPerson: return "information desk person, informationdeskperson, information_desk_person" - case .manTippingHand: return "man-tipping-hand, man tipping hand, mantippinghand" + case .womanGesturingOk: return "woman gesturing ok, woman-gesturing-ok, womangesturingok" + case .informationDeskPerson: return "information desk person, information_desk_person, informationdeskperson" + case .manTippingHand: return "man tipping hand, man-tipping-hand, mantippinghand" case .womanTippingHand: return "woman tipping hand, woman-tipping-hand, womantippinghand" - case .raisingHand: return "happy person raising one hand, raisinghand, raising_hand" - case .manRaisingHand: return "manraisinghand, man-raising-hand, man raising hand" - case .womanRaisingHand: return "woman-raising-hand, woman raising hand, womanraisinghand" - case .deafPerson: return "deafperson, deaf_person, deaf person" - case .deafMan: return "deafman, deaf_man, deaf man" + case .raisingHand: return "happy person raising one hand, raising_hand, raisinghand" + case .manRaisingHand: return "man raising hand, man-raising-hand, manraisinghand" + case .womanRaisingHand: return "woman raising hand, woman-raising-hand, womanraisinghand" + case .deafPerson: return "deaf person, deaf_person, deafperson" + case .deafMan: return "deaf man, deaf_man, deafman" case .deafWoman: return "deaf woman, deaf_woman, deafwoman" - case .bow: return "person bowing deeply, bow" - case .manBowing: return "manbowing, man-bowing, man bowing" - case .womanBowing: return "woman-bowing, womanbowing, woman bowing" - case .facePalm: return "face palm, facepalm, face_palm" - case .manFacepalming: return "man-facepalming, man facepalming, manfacepalming" + case .bow: return "bow, person bowing deeply" + case .manBowing: return "man bowing, man-bowing, manbowing" + case .womanBowing: return "woman bowing, woman-bowing, womanbowing" + case .facePalm: return "face palm, face_palm, facepalm" + case .manFacepalming: return "man facepalming, man-facepalming, manfacepalming" case .womanFacepalming: return "woman facepalming, woman-facepalming, womanfacepalming" case .shrug: return "shrug" - case .manShrugging: return "manshrugging, man-shrugging, man shrugging" - case .womanShrugging: return "woman-shrugging, womanshrugging, woman shrugging" - case .healthWorker: return "health_worker, health worker, healthworker" + case .manShrugging: return "man shrugging, man-shrugging, manshrugging" + case .womanShrugging: return "woman shrugging, woman-shrugging, womanshrugging" + case .healthWorker: return "health worker, health_worker, healthworker" case .maleDoctor: return "male-doctor, maledoctor, man health worker" - case .femaleDoctor: return "woman health worker, femaledoctor, female-doctor" + case .femaleDoctor: return "female-doctor, femaledoctor, woman health worker" case .student: return "student" case .maleStudent: return "male-student, malestudent, man student" - case .femaleStudent: return "femalestudent, woman student, female-student" + case .femaleStudent: return "female-student, femalestudent, woman student" case .teacher: return "teacher" case .maleTeacher: return "male-teacher, maleteacher, man teacher" - case .femaleTeacher: return "woman teacher, female-teacher, femaleteacher" + case .femaleTeacher: return "female-teacher, femaleteacher, woman teacher" case .judge: return "judge" - case .maleJudge: return "man judge, male-judge, malejudge" - case .femaleJudge: return "female-judge, woman judge, femalejudge" + case .maleJudge: return "male-judge, malejudge, man judge" + case .femaleJudge: return "female-judge, femalejudge, woman judge" case .farmer: return "farmer" - case .maleFarmer: return "male-farmer, man farmer, malefarmer" - case .femaleFarmer: return "femalefarmer, female-farmer, woman farmer" + case .maleFarmer: return "male-farmer, malefarmer, man farmer" + case .femaleFarmer: return "female-farmer, femalefarmer, woman farmer" case .cook: return "cook" - case .maleCook: return "man cook, malecook, male-cook" - case .femaleCook: return "female-cook, woman cook, femalecook" + case .maleCook: return "male-cook, malecook, man cook" + case .femaleCook: return "female-cook, femalecook, woman cook" case .mechanic: return "mechanic" - case .maleMechanic: return "malemechanic, male-mechanic, man mechanic" - case .femaleMechanic: return "female-mechanic, woman mechanic, femalemechanic" - case .factoryWorker: return "factory_worker, factory worker, factoryworker" - case .maleFactoryWorker: return "man factory worker, male-factory-worker, malefactoryworker" - case .femaleFactoryWorker: return "female-factory-worker, woman factory worker, femalefactoryworker" - case .officeWorker: return "officeworker, office_worker, office worker" + case .maleMechanic: return "male-mechanic, malemechanic, man mechanic" + case .femaleMechanic: return "female-mechanic, femalemechanic, woman mechanic" + case .factoryWorker: return "factory worker, factory_worker, factoryworker" + case .maleFactoryWorker: return "male-factory-worker, malefactoryworker, man factory worker" + case .femaleFactoryWorker: return "female-factory-worker, femalefactoryworker, woman factory worker" + case .officeWorker: return "office worker, office_worker, officeworker" case .maleOfficeWorker: return "male-office-worker, maleofficeworker, man office worker" - case .femaleOfficeWorker: return "female-office-worker, woman office worker, femaleofficeworker" + case .femaleOfficeWorker: return "female-office-worker, femaleofficeworker, woman office worker" case .scientist: return "scientist" - case .maleScientist: return "man scientist, malescientist, male-scientist" + case .maleScientist: return "male-scientist, malescientist, man scientist" case .femaleScientist: return "female-scientist, femalescientist, woman scientist" case .technologist: return "technologist" case .maleTechnologist: return "male-technologist, maletechnologist, man technologist" - case .femaleTechnologist: return "femaletechnologist, woman technologist, female-technologist" + case .femaleTechnologist: return "female-technologist, femaletechnologist, woman technologist" case .singer: return "singer" - case .maleSinger: return "male-singer, man singer, malesinger" - case .femaleSinger: return "woman singer, femalesinger, female-singer" + case .maleSinger: return "male-singer, malesinger, man singer" + case .femaleSinger: return "female-singer, femalesinger, woman singer" case .artist: return "artist" - case .maleArtist: return "man artist, maleartist, male-artist" - case .femaleArtist: return "femaleartist, female-artist, woman artist" + case .maleArtist: return "male-artist, maleartist, man artist" + case .femaleArtist: return "female-artist, femaleartist, woman artist" case .pilot: return "pilot" case .malePilot: return "male-pilot, malepilot, man pilot" - case .femalePilot: return "female-pilot, woman pilot, femalepilot" + case .femalePilot: return "female-pilot, femalepilot, woman pilot" case .astronaut: return "astronaut" - case .maleAstronaut: return "man astronaut, male-astronaut, maleastronaut" - case .femaleAstronaut: return "femaleastronaut, female-astronaut, woman astronaut" + case .maleAstronaut: return "male-astronaut, maleastronaut, man astronaut" + case .femaleAstronaut: return "female-astronaut, femaleastronaut, woman astronaut" case .firefighter: return "firefighter" - case .maleFirefighter: return "man firefighter, male-firefighter, malefirefighter" - case .femaleFirefighter: return "woman firefighter, femalefirefighter, female-firefighter" - case .cop: return "police officer, cop" - case .malePoliceOfficer: return "malepoliceofficer, male-police-officer, man police officer" - case .femalePoliceOfficer: return "woman police officer, female-police-officer, femalepoliceofficer" - case .sleuthOrSpy: return "sleuthorspy, sleuth_or_spy, detective" - case .maleDetective: return "maledetective, male-detective, man detective" - case .femaleDetective: return "woman detective, female-detective, femaledetective" + case .maleFirefighter: return "male-firefighter, malefirefighter, man firefighter" + case .femaleFirefighter: return "female-firefighter, femalefirefighter, woman firefighter" + case .cop: return "cop, police officer" + case .malePoliceOfficer: return "male-police-officer, malepoliceofficer, man police officer" + case .femalePoliceOfficer: return "female-police-officer, femalepoliceofficer, woman police officer" + case .sleuthOrSpy: return "detective, sleuth_or_spy, sleuthorspy" + case .maleDetective: return "male-detective, maledetective, man detective" + case .femaleDetective: return "female-detective, femaledetective, woman detective" case .guardsman: return "guardsman" - case .maleGuard: return "maleguard, male-guard, man guard" - case .femaleGuard: return "woman guard, female-guard, femaleguard" + case .maleGuard: return "male-guard, maleguard, man guard" + case .femaleGuard: return "female-guard, femaleguard, woman guard" case .ninja: return "ninja" - case .constructionWorker: return "construction_worker, constructionworker, construction worker" - case .maleConstructionWorker: return "male-construction-worker, man construction worker, maleconstructionworker" - case .femaleConstructionWorker: return "femaleconstructionworker, female-construction-worker, woman construction worker" - case .personWithCrown: return "person_with_crown, person with crown, personwithcrown" + case .constructionWorker: return "construction worker, construction_worker, constructionworker" + case .maleConstructionWorker: return "male-construction-worker, maleconstructionworker, man construction worker" + case .femaleConstructionWorker: return "female-construction-worker, femaleconstructionworker, woman construction worker" + case .personWithCrown: return "person with crown, person_with_crown, personwithcrown" case .prince: return "prince" case .princess: return "princess" - case .manWithTurban: return "man with turban, manwithturban, man_with_turban" - case .manWearingTurban: return "man-wearing-turban, manwearingturban, man wearing turban" - case .womanWearingTurban: return "woman-wearing-turban, womanwearingturban, woman wearing turban" - case .manWithGuaPiMao: return "man_with_gua_pi_mao, man with gua pi mao, manwithguapimao" - case .personWithHeadscarf: return "person_with_headscarf, personwithheadscarf, person with headscarf" - case .personInTuxedo: return "person_in_tuxedo, personintuxedo, man in tuxedo" - case .manInTuxedo: return "man_in_tuxedo, manintuxedo, man in tuxedo" - case .womanInTuxedo: return "womanintuxedo, woman in tuxedo, woman_in_tuxedo" - case .brideWithVeil: return "bridewithveil, bride_with_veil, bride with veil" - case .manWithVeil: return "man_with_veil, man with veil, manwithveil" - case .womanWithVeil: return "woman with veil, womanwithveil, woman_with_veil" - case .pregnantWoman: return "pregnant woman, pregnantwoman, pregnant_woman" - case .pregnantMan: return "pregnant_man, pregnant man, pregnantman" - case .pregnantPerson: return "pregnant_person, pregnant person, pregnantperson" + case .manWithTurban: return "man with turban, man_with_turban, manwithturban" + case .manWearingTurban: return "man wearing turban, man-wearing-turban, manwearingturban" + case .womanWearingTurban: return "woman wearing turban, woman-wearing-turban, womanwearingturban" + case .manWithGuaPiMao: return "man with gua pi mao, man_with_gua_pi_mao, manwithguapimao" + case .personWithHeadscarf: return "person with headscarf, person_with_headscarf, personwithheadscarf" + case .personInTuxedo: return "man in tuxedo, person_in_tuxedo, personintuxedo" + case .manInTuxedo: return "man in tuxedo, man_in_tuxedo, manintuxedo" + case .womanInTuxedo: return "woman in tuxedo, woman_in_tuxedo, womanintuxedo" + case .brideWithVeil: return "bride with veil, bride_with_veil, bridewithveil" + case .manWithVeil: return "man with veil, man_with_veil, manwithveil" + case .womanWithVeil: return "woman with veil, woman_with_veil, womanwithveil" + case .pregnantWoman: return "pregnant woman, pregnant_woman, pregnantwoman" + case .pregnantMan: return "pregnant man, pregnant_man, pregnantman" + case .pregnantPerson: return "pregnant person, pregnant_person, pregnantperson" case .breastFeeding: return "breast-feeding, breastfeeding" - case .womanFeedingBaby: return "womanfeedingbaby, woman_feeding_baby, woman feeding baby" - case .manFeedingBaby: return "man feeding baby, manfeedingbaby, man_feeding_baby" - case .personFeedingBaby: return "personfeedingbaby, person_feeding_baby, person feeding baby" + case .womanFeedingBaby: return "woman feeding baby, woman_feeding_baby, womanfeedingbaby" + case .manFeedingBaby: return "man feeding baby, man_feeding_baby, manfeedingbaby" + case .personFeedingBaby: return "person feeding baby, person_feeding_baby, personfeedingbaby" case .angel: return "angel, baby angel" case .santa: return "father christmas, santa" - case .mrsClaus: return "mrsclaus, mother_christmas, mother christmas, mrs_claus" - case .mxClaus: return "mxclaus, mx claus, mx_claus" + case .mrsClaus: return "mother christmas, mother_christmas, mrs_claus, mrsclaus" + case .mxClaus: return "mx claus, mx_claus, mxclaus" case .superhero: return "superhero" - case .maleSuperhero: return "male_superhero, man superhero, malesuperhero" + case .maleSuperhero: return "male_superhero, malesuperhero, man superhero" case .femaleSuperhero: return "female_superhero, femalesuperhero, woman superhero" case .supervillain: return "supervillain" - case .maleSupervillain: return "malesupervillain, man supervillain, male_supervillain" - case .femaleSupervillain: return "female_supervillain, woman supervillain, femalesupervillain" + case .maleSupervillain: return "male_supervillain, malesupervillain, man supervillain" + case .femaleSupervillain: return "female_supervillain, femalesupervillain, woman supervillain" case .mage: return "mage" case .maleMage: return "male_mage, malemage, man mage" - case .femaleMage: return "female_mage, woman mage, femalemage" + case .femaleMage: return "female_mage, femalemage, woman mage" case .fairy: return "fairy" - case .maleFairy: return "malefairy, man fairy, male_fairy" - case .femaleFairy: return "femalefairy, female_fairy, woman fairy" + case .maleFairy: return "male_fairy, malefairy, man fairy" + case .femaleFairy: return "female_fairy, femalefairy, woman fairy" case .vampire: return "vampire" - case .maleVampire: return "malevampire, male_vampire, man vampire" - case .femaleVampire: return "female_vampire, woman vampire, femalevampire" + case .maleVampire: return "male_vampire, malevampire, man vampire" + case .femaleVampire: return "female_vampire, femalevampire, woman vampire" case .merperson: return "merperson" case .merman: return "merman" case .mermaid: return "mermaid" case .elf: return "elf" - case .maleElf: return "male_elf, man elf, maleelf" + case .maleElf: return "male_elf, maleelf, man elf" case .femaleElf: return "female_elf, femaleelf, woman elf" case .genie: return "genie" - case .maleGenie: return "man genie, male_genie, malegenie" - case .femaleGenie: return "woman genie, femalegenie, female_genie" + case .maleGenie: return "male_genie, malegenie, man genie" + case .femaleGenie: return "female_genie, femalegenie, woman genie" case .zombie: return "zombie" - case .maleZombie: return "malezombie, man zombie, male_zombie" - case .femaleZombie: return "woman zombie, female_zombie, femalezombie" + case .maleZombie: return "male_zombie, malezombie, man zombie" + case .femaleZombie: return "female_zombie, femalezombie, woman zombie" case .troll: return "troll" - case .massage: return "massage, face massage" + case .massage: return "face massage, massage" case .manGettingMassage: return "man getting massage, man-getting-massage, mangettingmassage" - case .womanGettingMassage: return "woman getting massage, womangettingmassage, woman-getting-massage" + case .womanGettingMassage: return "woman getting massage, woman-getting-massage, womangettingmassage" case .haircut: return "haircut" - case .manGettingHaircut: return "mangettinghaircut, man-getting-haircut, man getting haircut" - case .womanGettingHaircut: return "woman-getting-haircut, woman getting haircut, womangettinghaircut" + case .manGettingHaircut: return "man getting haircut, man-getting-haircut, mangettinghaircut" + case .womanGettingHaircut: return "woman getting haircut, woman-getting-haircut, womangettinghaircut" case .walking: return "pedestrian, walking" - case .manWalking: return "man-walking, man walking, manwalking" - case .womanWalking: return "woman-walking, woman walking, womanwalking" - case .standingPerson: return "standing person, standingperson, standing_person" - case .manStanding: return "manstanding, man_standing, man standing" - case .womanStanding: return "woman_standing, womanstanding, woman standing" - case .kneelingPerson: return "kneelingperson, kneeling person, kneeling_person" + case .manWalking: return "man walking, man-walking, manwalking" + case .womanWalking: return "woman walking, woman-walking, womanwalking" + case .standingPerson: return "standing person, standing_person, standingperson" + case .manStanding: return "man standing, man_standing, manstanding" + case .womanStanding: return "woman standing, woman_standing, womanstanding" + case .kneelingPerson: return "kneeling person, kneeling_person, kneelingperson" case .manKneeling: return "man kneeling, man_kneeling, mankneeling" - case .womanKneeling: return "woman_kneeling, woman kneeling, womankneeling" - case .personWithProbingCane: return "person_with_probing_cane, personwithprobingcane, person with white cane" - case .manWithProbingCane: return "man with white cane, manwithprobingcane, man_with_probing_cane" - case .womanWithProbingCane: return "woman_with_probing_cane, womanwithprobingcane, woman with white cane" - case .personInMotorizedWheelchair: return "personinmotorizedwheelchair, person in motorized wheelchair, person_in_motorized_wheelchair" - case .manInMotorizedWheelchair: return "man in motorized wheelchair, maninmotorizedwheelchair, man_in_motorized_wheelchair" - case .womanInMotorizedWheelchair: return "woman in motorized wheelchair, womaninmotorizedwheelchair, woman_in_motorized_wheelchair" - case .personInManualWheelchair: return "personinmanualwheelchair, person_in_manual_wheelchair, person in manual wheelchair" - case .manInManualWheelchair: return "man_in_manual_wheelchair, maninmanualwheelchair, man in manual wheelchair" - case .womanInManualWheelchair: return "womaninmanualwheelchair, woman_in_manual_wheelchair, woman in manual wheelchair" - case .runner: return "running, runner" - case .manRunning: return "man-running, man running, manrunning" - case .womanRunning: return "woman running, womanrunning, woman-running" + case .womanKneeling: return "woman kneeling, woman_kneeling, womankneeling" + case .personWithProbingCane: return "person with white cane, person_with_probing_cane, personwithprobingcane" + case .manWithProbingCane: return "man with white cane, man_with_probing_cane, manwithprobingcane" + case .womanWithProbingCane: return "woman with white cane, woman_with_probing_cane, womanwithprobingcane" + case .personInMotorizedWheelchair: return "person in motorized wheelchair, person_in_motorized_wheelchair, personinmotorizedwheelchair" + case .manInMotorizedWheelchair: return "man in motorized wheelchair, man_in_motorized_wheelchair, maninmotorizedwheelchair" + case .womanInMotorizedWheelchair: return "woman in motorized wheelchair, woman_in_motorized_wheelchair, womaninmotorizedwheelchair" + case .personInManualWheelchair: return "person in manual wheelchair, person_in_manual_wheelchair, personinmanualwheelchair" + case .manInManualWheelchair: return "man in manual wheelchair, man_in_manual_wheelchair, maninmanualwheelchair" + case .womanInManualWheelchair: return "woman in manual wheelchair, woman_in_manual_wheelchair, womaninmanualwheelchair" + case .runner: return "runner, running" + case .manRunning: return "man running, man-running, manrunning" + case .womanRunning: return "woman running, woman-running, womanrunning" case .dancer: return "dancer" - case .manDancing: return "man_dancing, mandancing, man dancing" - case .manInBusinessSuitLevitating: return "maninbusinesssuitlevitating, man_in_business_suit_levitating, person in suit levitating" + case .manDancing: return "man dancing, man_dancing, mandancing" + case .manInBusinessSuitLevitating: return "man_in_business_suit_levitating, maninbusinesssuitlevitating, person in suit levitating" case .dancers: return "dancers, woman with bunny ears" - case .menWithBunnyEarsPartying: return "menwithbunnyearspartying, man-with-bunny-ears-partying, men with bunny ears, men-with-bunny-ears-partying" - case .womenWithBunnyEarsPartying: return "women-with-bunny-ears-partying, woman-with-bunny-ears-partying, womenwithbunnyearspartying, women with bunny ears" - case .personInSteamyRoom: return "person_in_steamy_room, person in steamy room, personinsteamyroom" + case .menWithBunnyEarsPartying: return "man-with-bunny-ears-partying, men with bunny ears, men-with-bunny-ears-partying, menwithbunnyearspartying" + case .womenWithBunnyEarsPartying: return "woman-with-bunny-ears-partying, women with bunny ears, women-with-bunny-ears-partying, womenwithbunnyearspartying" + case .personInSteamyRoom: return "person in steamy room, person_in_steamy_room, personinsteamyroom" case .manInSteamyRoom: return "man in steamy room, man_in_steamy_room, maninsteamyroom" case .womanInSteamyRoom: return "woman in steamy room, woman_in_steamy_room, womaninsteamyroom" - case .personClimbing: return "person_climbing, person climbing, personclimbing" + case .personClimbing: return "person climbing, person_climbing, personclimbing" case .manClimbing: return "man climbing, man_climbing, manclimbing" - case .womanClimbing: return "woman climbing, womanclimbing, woman_climbing" + case .womanClimbing: return "woman climbing, woman_climbing, womanclimbing" case .fencer: return "fencer" - case .horseRacing: return "horse_racing, horseracing, horse racing" + case .horseRacing: return "horse racing, horse_racing, horseracing" case .skier: return "skier" case .snowboarder: return "snowboarder" case .golfer: return "golfer, person golfing" - case .manGolfing: return "mangolfing, man-golfing, man golfing" - case .womanGolfing: return "womangolfing, woman golfing, woman-golfing" + case .manGolfing: return "man golfing, man-golfing, mangolfing" + case .womanGolfing: return "woman golfing, woman-golfing, womangolfing" case .surfer: return "surfer" case .manSurfing: return "man surfing, man-surfing, mansurfing" - case .womanSurfing: return "woman surfing, womansurfing, woman-surfing" + case .womanSurfing: return "woman surfing, woman-surfing, womansurfing" case .rowboat: return "rowboat" - case .manRowingBoat: return "man-rowing-boat, man rowing boat, manrowingboat" - case .womanRowingBoat: return "womanrowingboat, woman rowing boat, woman-rowing-boat" + case .manRowingBoat: return "man rowing boat, man-rowing-boat, manrowingboat" + case .womanRowingBoat: return "woman rowing boat, woman-rowing-boat, womanrowingboat" case .swimmer: return "swimmer" - case .manSwimming: return "man swimming, manswimming, man-swimming" - case .womanSwimming: return "woman swimming, womanswimming, woman-swimming" - case .personWithBall: return "person_with_ball, person bouncing ball, personwithball" - case .manBouncingBall: return "manbouncingball, man bouncing ball, man-bouncing-ball" - case .womanBouncingBall: return "woman-bouncing-ball, woman bouncing ball, womanbouncingball" + case .manSwimming: return "man swimming, man-swimming, manswimming" + case .womanSwimming: return "woman swimming, woman-swimming, womanswimming" + case .personWithBall: return "person bouncing ball, person_with_ball, personwithball" + case .manBouncingBall: return "man bouncing ball, man-bouncing-ball, manbouncingball" + case .womanBouncingBall: return "woman bouncing ball, woman-bouncing-ball, womanbouncingball" case .weightLifter: return "person lifting weights, weight_lifter, weightlifter" - case .manLiftingWeights: return "manliftingweights, man-lifting-weights, man lifting weights" - case .womanLiftingWeights: return "woman-lifting-weights, woman lifting weights, womanliftingweights" + case .manLiftingWeights: return "man lifting weights, man-lifting-weights, manliftingweights" + case .womanLiftingWeights: return "woman lifting weights, woman-lifting-weights, womanliftingweights" case .bicyclist: return "bicyclist" - case .manBiking: return "man biking, manbiking, man-biking" - case .womanBiking: return "woman biking, womanbiking, woman-biking" - case .mountainBicyclist: return "mountain_bicyclist, mountain bicyclist, mountainbicyclist" - case .manMountainBiking: return "man mountain biking, manmountainbiking, man-mountain-biking" - case .womanMountainBiking: return "woman-mountain-biking, womanmountainbiking, woman mountain biking" - case .personDoingCartwheel: return "person doing cartwheel, persondoingcartwheel, person_doing_cartwheel" - case .manCartwheeling: return "man-cartwheeling, mancartwheeling, man cartwheeling" - case .womanCartwheeling: return "woman-cartwheeling, woman cartwheeling, womancartwheeling" + case .manBiking: return "man biking, man-biking, manbiking" + case .womanBiking: return "woman biking, woman-biking, womanbiking" + case .mountainBicyclist: return "mountain bicyclist, mountain_bicyclist, mountainbicyclist" + case .manMountainBiking: return "man mountain biking, man-mountain-biking, manmountainbiking" + case .womanMountainBiking: return "woman mountain biking, woman-mountain-biking, womanmountainbiking" + case .personDoingCartwheel: return "person doing cartwheel, person_doing_cartwheel, persondoingcartwheel" + case .manCartwheeling: return "man cartwheeling, man-cartwheeling, mancartwheeling" + case .womanCartwheeling: return "woman cartwheeling, woman-cartwheeling, womancartwheeling" case .wrestlers: return "wrestlers" case .manWrestling: return "man-wrestling, manwrestling, men wrestling" - case .womanWrestling: return "womanwrestling, women wrestling, woman-wrestling" - case .waterPolo: return "water polo, waterpolo, water_polo" - case .manPlayingWaterPolo: return "man playing water polo, manplayingwaterpolo, man-playing-water-polo" - case .womanPlayingWaterPolo: return "womanplayingwaterpolo, woman playing water polo, woman-playing-water-polo" + case .womanWrestling: return "woman-wrestling, womanwrestling, women wrestling" + case .waterPolo: return "water polo, water_polo, waterpolo" + case .manPlayingWaterPolo: return "man playing water polo, man-playing-water-polo, manplayingwaterpolo" + case .womanPlayingWaterPolo: return "woman playing water polo, woman-playing-water-polo, womanplayingwaterpolo" case .handball: return "handball" - case .manPlayingHandball: return "man-playing-handball, manplayinghandball, man playing handball" - case .womanPlayingHandball: return "womanplayinghandball, woman playing handball, woman-playing-handball" + case .manPlayingHandball: return "man playing handball, man-playing-handball, manplayinghandball" + case .womanPlayingHandball: return "woman playing handball, woman-playing-handball, womanplayinghandball" case .juggling: return "juggling" - case .manJuggling: return "man-juggling, manjuggling, man juggling" - case .womanJuggling: return "woman-juggling, womanjuggling, woman juggling" - case .personInLotusPosition: return "personinlotusposition, person in lotus position, person_in_lotus_position" - case .manInLotusPosition: return "maninlotusposition, man_in_lotus_position, man in lotus position" - case .womanInLotusPosition: return "woman_in_lotus_position, woman in lotus position, womaninlotusposition" + case .manJuggling: return "man juggling, man-juggling, manjuggling" + case .womanJuggling: return "woman juggling, woman-juggling, womanjuggling" + case .personInLotusPosition: return "person in lotus position, person_in_lotus_position, personinlotusposition" + case .manInLotusPosition: return "man in lotus position, man_in_lotus_position, maninlotusposition" + case .womanInLotusPosition: return "woman in lotus position, woman_in_lotus_position, womaninlotusposition" case .bath: return "bath" - case .sleepingAccommodation: return "sleeping_accommodation, sleeping accommodation, sleepingaccommodation" - case .peopleHoldingHands: return "peopleholdinghands, people_holding_hands, people holding hands" - case .twoWomenHoldingHands: return "twowomenholdinghands, two women holding hands, women_holding_hands, two_women_holding_hands" - case .manAndWomanHoldingHands: return "man and woman holding hands, couple, man_and_woman_holding_hands, manandwomanholdinghands, woman_and_man_holding_hands" - case .twoMenHoldingHands: return "two_men_holding_hands, twomenholdinghands, men_holding_hands, two men holding hands" - case .personKissPerson: return "kiss, personkissperson, couplekiss" - case .womanKissMan: return "womankissman, kiss: woman, man, woman-kiss-man" - case .manKissMan: return "mankissman, man-kiss-man, kiss: man, man" - case .womanKissWoman: return "kiss: woman, woman, womankisswoman, woman-kiss-woman" - case .personHeartPerson: return "couple_with_heart, personheartperson, couple with heart" - case .womanHeartMan: return "womanheartman, woman-heart-man, couple with heart: woman, man" - case .manHeartMan: return "man-heart-man, couple with heart: man, man, manheartman" - case .womanHeartWoman: return "couple with heart: woman, woman, womanheartwoman, woman-heart-woman" + case .sleepingAccommodation: return "sleeping accommodation, sleeping_accommodation, sleepingaccommodation" + case .peopleHoldingHands: return "people holding hands, people_holding_hands, peopleholdinghands" + case .twoWomenHoldingHands: return "two women holding hands, two_women_holding_hands, twowomenholdinghands, women_holding_hands" + case .manAndWomanHoldingHands: return "couple, man and woman holding hands, man_and_woman_holding_hands, manandwomanholdinghands, woman_and_man_holding_hands" + case .twoMenHoldingHands: return "men_holding_hands, two men holding hands, two_men_holding_hands, twomenholdinghands" + case .personKissPerson: return "couplekiss, kiss, personkissperson" + case .womanKissMan: return "kiss: woman, man, woman-kiss-man, womankissman" + case .manKissMan: return "kiss: man, man, man-kiss-man, mankissman" + case .womanKissWoman: return "kiss: woman, woman, woman-kiss-woman, womankisswoman" + case .personHeartPerson: return "couple with heart, couple_with_heart, personheartperson" + case .womanHeartMan: return "couple with heart: woman, man, woman-heart-man, womanheartman" + case .manHeartMan: return "couple with heart: man, man, man-heart-man, manheartman" + case .womanHeartWoman: return "couple with heart: woman, woman, woman-heart-woman, womanheartwoman" case .family: return "family" case .manWomanBoy: return "family: man, woman, boy, man-woman-boy, manwomanboy" - case .manWomanGirl: return "manwomangirl, man-woman-girl, family: man, woman, girl" - case .manWomanGirlBoy: return "man-woman-girl-boy, family: man, woman, girl, boy, manwomangirlboy" + case .manWomanGirl: return "family: man, woman, girl, man-woman-girl, manwomangirl" + case .manWomanGirlBoy: return "family: man, woman, girl, boy, man-woman-girl-boy, manwomangirlboy" case .manWomanBoyBoy: return "family: man, woman, boy, boy, man-woman-boy-boy, manwomanboyboy" - case .manWomanGirlGirl: return "man-woman-girl-girl, family: man, woman, girl, girl, manwomangirlgirl" - case .manManBoy: return "manmanboy, man-man-boy, family: man, man, boy" - case .manManGirl: return "manmangirl, family: man, man, girl, man-man-girl" - case .manManGirlBoy: return "manmangirlboy, man-man-girl-boy, family: man, man, girl, boy" - case .manManBoyBoy: return "manmanboyboy, man-man-boy-boy, family: man, man, boy, boy" - case .manManGirlGirl: return "man-man-girl-girl, family: man, man, girl, girl, manmangirlgirl" - case .womanWomanBoy: return "family: woman, woman, boy, womanwomanboy, woman-woman-boy" - case .womanWomanGirl: return "woman-woman-girl, family: woman, woman, girl, womanwomangirl" - case .womanWomanGirlBoy: return "family: woman, woman, girl, boy, womanwomangirlboy, woman-woman-girl-boy" - case .womanWomanBoyBoy: return "womanwomanboyboy, woman-woman-boy-boy, family: woman, woman, boy, boy" - case .womanWomanGirlGirl: return "family: woman, woman, girl, girl, womanwomangirlgirl, woman-woman-girl-girl" - case .manBoy: return "manboy, man-boy, family: man, boy" - case .manBoyBoy: return "man-boy-boy, family: man, boy, boy, manboyboy" - case .manGirl: return "man-girl, mangirl, family: man, girl" - case .manGirlBoy: return "man-girl-boy, family: man, girl, boy, mangirlboy" - case .manGirlGirl: return "mangirlgirl, man-girl-girl, family: man, girl, girl" - case .womanBoy: return "womanboy, woman-boy, family: woman, boy" + case .manWomanGirlGirl: return "family: man, woman, girl, girl, man-woman-girl-girl, manwomangirlgirl" + case .manManBoy: return "family: man, man, boy, man-man-boy, manmanboy" + case .manManGirl: return "family: man, man, girl, man-man-girl, manmangirl" + case .manManGirlBoy: return "family: man, man, girl, boy, man-man-girl-boy, manmangirlboy" + case .manManBoyBoy: return "family: man, man, boy, boy, man-man-boy-boy, manmanboyboy" + case .manManGirlGirl: return "family: man, man, girl, girl, man-man-girl-girl, manmangirlgirl" + case .womanWomanBoy: return "family: woman, woman, boy, woman-woman-boy, womanwomanboy" + case .womanWomanGirl: return "family: woman, woman, girl, woman-woman-girl, womanwomangirl" + case .womanWomanGirlBoy: return "family: woman, woman, girl, boy, woman-woman-girl-boy, womanwomangirlboy" + case .womanWomanBoyBoy: return "family: woman, woman, boy, boy, woman-woman-boy-boy, womanwomanboyboy" + case .womanWomanGirlGirl: return "family: woman, woman, girl, girl, woman-woman-girl-girl, womanwomangirlgirl" + case .manBoy: return "family: man, boy, man-boy, manboy" + case .manBoyBoy: return "family: man, boy, boy, man-boy-boy, manboyboy" + case .manGirl: return "family: man, girl, man-girl, mangirl" + case .manGirlBoy: return "family: man, girl, boy, man-girl-boy, mangirlboy" + case .manGirlGirl: return "family: man, girl, girl, man-girl-girl, mangirlgirl" + case .womanBoy: return "family: woman, boy, woman-boy, womanboy" case .womanBoyBoy: return "family: woman, boy, boy, woman-boy-boy, womanboyboy" - case .womanGirl: return "family: woman, girl, womangirl, woman-girl" - case .womanGirlBoy: return "family: woman, girl, boy, womangirlboy, woman-girl-boy" - case .womanGirlGirl: return "woman-girl-girl, womangirlgirl, family: woman, girl, girl" - case .speakingHeadInSilhouette: return "speaking_head_in_silhouette, speaking head, speakingheadinsilhouette" - case .bustInSilhouette: return "bustinsilhouette, bust_in_silhouette, bust in silhouette" - case .bustsInSilhouette: return "busts_in_silhouette, busts in silhouette, bustsinsilhouette" - case .peopleHugging: return "people_hugging, people hugging, peoplehugging" + case .womanGirl: return "family: woman, girl, woman-girl, womangirl" + case .womanGirlBoy: return "family: woman, girl, boy, woman-girl-boy, womangirlboy" + case .womanGirlGirl: return "family: woman, girl, girl, woman-girl-girl, womangirlgirl" + case .speakingHeadInSilhouette: return "speaking head, speaking_head_in_silhouette, speakingheadinsilhouette" + case .bustInSilhouette: return "bust in silhouette, bust_in_silhouette, bustinsilhouette" + case .bustsInSilhouette: return "busts in silhouette, busts_in_silhouette, bustsinsilhouette" + case .peopleHugging: return "people hugging, people_hugging, peoplehugging" case .footprints: return "footprints" - case .skinTone2: return "skin-tone-2, emoji modifier fitzpatrick type-1-2, skintone2" - case .skinTone3: return "skintone3, skin-tone-3, emoji modifier fitzpatrick type-3" - case .skinTone4: return "skintone4, skin-tone-4, emoji modifier fitzpatrick type-4" - case .skinTone5: return "emoji modifier fitzpatrick type-5, skintone5, skin-tone-5" - case .skinTone6: return "skin-tone-6, emoji modifier fitzpatrick type-6, skintone6" - case .monkeyFace: return "monkeyface, monkey_face, monkey face" + case .skinTone2: return "emoji modifier fitzpatrick type-1-2, skin-tone-2, skintone2" + case .skinTone3: return "emoji modifier fitzpatrick type-3, skin-tone-3, skintone3" + case .skinTone4: return "emoji modifier fitzpatrick type-4, skin-tone-4, skintone4" + case .skinTone5: return "emoji modifier fitzpatrick type-5, skin-tone-5, skintone5" + case .skinTone6: return "emoji modifier fitzpatrick type-6, skin-tone-6, skintone6" + case .monkeyFace: return "monkey face, monkey_face, monkeyface" case .monkey: return "monkey" case .gorilla: return "gorilla" case .orangutan: return "orangutan" - case .dog: return "dog face, dog" + case .dog: return "dog, dog face" case .dog2: return "dog, dog2" - case .guideDog: return "guide_dog, guidedog, guide dog" + case .guideDog: return "guide dog, guide_dog, guidedog" case .serviceDog: return "service dog, service_dog, servicedog" case .poodle: return "poodle" case .wolf: return "wolf, wolf face" - case .foxFace: return "foxface, fox_face, fox face" + case .foxFace: return "fox face, fox_face, foxface" case .raccoon: return "raccoon" case .cat: return "cat, cat face" - case .cat2: return "cat2, cat" - case .blackCat: return "black cat, blackcat, black_cat" - case .lionFace: return "lion_face, lion face, lionface" - case .tiger: return "tiger face, tiger" + case .cat2: return "cat, cat2" + case .blackCat: return "black cat, black_cat, blackcat" + case .lionFace: return "lion face, lion_face, lionface" + case .tiger: return "tiger, tiger face" case .tiger2: return "tiger, tiger2" case .leopard: return "leopard" - case .horse: return "horse face, horse" + case .horse: return "horse, horse face" + case .moose: return "moose" + case .donkey: return "donkey" case .racehorse: return "horse, racehorse" - case .unicornFace: return "unicorn_face, unicorn face, unicornface" - case .zebraFace: return "zebra_face, zebra face, zebraface" + case .unicornFace: return "unicorn face, unicorn_face, unicornface" + case .zebraFace: return "zebra face, zebra_face, zebraface" case .deer: return "deer" case .bison: return "bison" - case .cow: return "cow face, cow" + case .cow: return "cow, cow face" case .ox: return "ox" - case .waterBuffalo: return "water buffalo, waterbuffalo, water_buffalo" + case .waterBuffalo: return "water buffalo, water_buffalo, waterbuffalo" case .cow2: return "cow, cow2" case .pig: return "pig, pig face" - case .pig2: return "pig2, pig" + case .pig2: return "pig, pig2" case .boar: return "boar" - case .pigNose: return "pig_nose, pig nose, pignose" + case .pigNose: return "pig nose, pig_nose, pignose" case .ram: return "ram" case .sheep: return "sheep" case .goat: return "goat" - case .dromedaryCamel: return "dromedary_camel, dromedary camel, dromedarycamel" + case .dromedaryCamel: return "dromedary camel, dromedary_camel, dromedarycamel" case .camel: return "bactrian camel, camel" case .llama: return "llama" case .giraffeFace: return "giraffe face, giraffe_face, giraffeface" @@ -578,34 +585,34 @@ extension Emoji { case .rhinoceros: return "rhinoceros" case .hippopotamus: return "hippopotamus" case .mouse: return "mouse, mouse face" - case .mouse2: return "mouse2, mouse" + case .mouse2: return "mouse, mouse2" case .rat: return "rat" case .hamster: return "hamster, hamster face" case .rabbit: return "rabbit, rabbit face" - case .rabbit2: return "rabbit2, rabbit" + case .rabbit2: return "rabbit, rabbit2" case .chipmunk: return "chipmunk" case .beaver: return "beaver" case .hedgehog: return "hedgehog" case .bat: return "bat" - case .bear: return "bear face, bear" - case .polarBear: return "polarbear, polar_bear, polar bear" + case .bear: return "bear, bear face" + case .polarBear: return "polar bear, polar_bear, polarbear" case .koala: return "koala" - case .pandaFace: return "panda face, pandaface, panda_face" + case .pandaFace: return "panda face, panda_face, pandaface" case .sloth: return "sloth" case .otter: return "otter" case .skunk: return "skunk" case .kangaroo: return "kangaroo" case .badger: return "badger" - case .feet: return "paw prints, feet, paw_prints" + case .feet: return "feet, paw prints, paw_prints" case .turkey: return "turkey" case .chicken: return "chicken" case .rooster: return "rooster" case .hatchingChick: return "hatching chick, hatching_chick, hatchingchick" - case .babyChick: return "baby_chick, baby chick, babychick" - case .hatchedChick: return "front-facing baby chick, hatchedchick, hatched_chick" + case .babyChick: return "baby chick, baby_chick, babychick" + case .hatchedChick: return "front-facing baby chick, hatched_chick, hatchedchick" case .bird: return "bird" case .penguin: return "penguin" - case .doveOfPeace: return "dove, doveofpeace, dove_of_peace" + case .doveOfPeace: return "dove, dove_of_peace, doveofpeace" case .eagle: return "eagle" case .duck: return "duck" case .swan: return "swan" @@ -615,6 +622,9 @@ extension Emoji { case .flamingo: return "flamingo" case .peacock: return "peacock" case .parrot: return "parrot" + case .wing: return "wing" + case .blackBird: return "black bird, black_bird, blackbird" + case .goose: return "goose" case .frog: return "frog, frog face" case .crocodile: return "crocodile" case .turtle: return "turtle" @@ -626,57 +636,60 @@ extension Emoji { case .tRex: return "t-rex, trex" case .whale: return "spouting whale, whale" case .whale2: return "whale, whale2" - case .dolphin: return "flipper, dolphin" + case .dolphin: return "dolphin, flipper" case .seal: return "seal" case .fish: return "fish" - case .tropicalFish: return "tropical_fish, tropical fish, tropicalfish" + case .tropicalFish: return "tropical fish, tropical_fish, tropicalfish" case .blowfish: return "blowfish" case .shark: return "shark" case .octopus: return "octopus" case .shell: return "shell, spiral shell" case .coral: return "coral" + case .jellyfish: return "jellyfish" case .snail: return "snail" case .butterfly: return "butterfly" case .bug: return "bug" case .ant: return "ant" case .bee: return "bee, honeybee" case .beetle: return "beetle" - case .ladybug: return "lady_beetle, lady beetle, ladybug" + case .ladybug: return "lady beetle, lady_beetle, ladybug" case .cricket: return "cricket" case .cockroach: return "cockroach" case .spider: return "spider" - case .spiderWeb: return "spiderweb, spider_web, spider web" + case .spiderWeb: return "spider web, spider_web, spiderweb" case .scorpion: return "scorpion" case .mosquito: return "mosquito" case .fly: return "fly" case .worm: return "worm" case .microbe: return "microbe" case .bouquet: return "bouquet" - case .cherryBlossom: return "cherryblossom, cherry_blossom, cherry blossom" - case .whiteFlower: return "white_flower, white flower, whiteflower" + case .cherryBlossom: return "cherry blossom, cherry_blossom, cherryblossom" + case .whiteFlower: return "white flower, white_flower, whiteflower" case .lotus: return "lotus" case .rosette: return "rosette" case .rose: return "rose" - case .wiltedFlower: return "wilted flower, wiltedflower, wilted_flower" + case .wiltedFlower: return "wilted flower, wilted_flower, wiltedflower" case .hibiscus: return "hibiscus" case .sunflower: return "sunflower" case .blossom: return "blossom" case .tulip: return "tulip" + case .hyacinth: return "hyacinth" case .seedling: return "seedling" case .pottedPlant: return "potted plant, potted_plant, pottedplant" - case .evergreenTree: return "evergreen tree, evergreentree, evergreen_tree" - case .deciduousTree: return "deciduous_tree, deciduous tree, deciduoustree" - case .palmTree: return "palm tree, palmtree, palm_tree" + case .evergreenTree: return "evergreen tree, evergreen_tree, evergreentree" + case .deciduousTree: return "deciduous tree, deciduous_tree, deciduoustree" + case .palmTree: return "palm tree, palm_tree, palmtree" case .cactus: return "cactus" - case .earOfRice: return "earofrice, ear of rice, ear_of_rice" + case .earOfRice: return "ear of rice, ear_of_rice, earofrice" case .herb: return "herb" case .shamrock: return "shamrock" case .fourLeafClover: return "four leaf clover, four_leaf_clover, fourleafclover" - case .mapleLeaf: return "maple_leaf, mapleleaf, maple leaf" - case .fallenLeaf: return "fallen_leaf, fallen leaf, fallenleaf" - case .leaves: return "leaves, leaf fluttering in wind" - case .emptyNest: return "emptynest, empty_nest, empty nest" - case .nestWithEggs: return "nest_with_eggs, nest with eggs, nestwitheggs" + case .mapleLeaf: return "maple leaf, maple_leaf, mapleleaf" + case .fallenLeaf: return "fallen leaf, fallen_leaf, fallenleaf" + case .leaves: return "leaf fluttering in wind, leaves" + case .emptyNest: return "empty nest, empty_nest, emptynest" + case .nestWithEggs: return "nest with eggs, nest_with_eggs, nestwitheggs" + case .mushroom: return "mushroom" case .grapes: return "grapes" case .melon: return "melon" case .watermelon: return "watermelon" @@ -685,7 +698,7 @@ extension Emoji { case .banana: return "banana" case .pineapple: return "pineapple" case .mango: return "mango" - case .apple: return "red apple, apple" + case .apple: return "apple, red apple" case .greenApple: return "green apple, green_apple, greenapple" case .pear: return "pear" case .peach: return "peach" @@ -700,18 +713,19 @@ extension Emoji { case .eggplant: return "aubergine, eggplant" case .potato: return "potato" case .carrot: return "carrot" - case .corn: return "ear of maize, corn" + case .corn: return "corn, ear of maize" case .hotPepper: return "hot pepper, hot_pepper, hotpepper" - case .bellPepper: return "bell pepper, bellpepper, bell_pepper" + case .bellPepper: return "bell pepper, bell_pepper, bellpepper" case .cucumber: return "cucumber" - case .leafyGreen: return "leafygreen, leafy_green, leafy green" + case .leafyGreen: return "leafy green, leafy_green, leafygreen" case .broccoli: return "broccoli" case .garlic: return "garlic" case .onion: return "onion" - case .mushroom: return "mushroom" case .peanuts: return "peanuts" case .beans: return "beans" case .chestnut: return "chestnut" + case .gingerRoot: return "ginger root, ginger_root, gingerroot" + case .peaPod: return "pea pod, pea_pod, peapod" case .bread: return "bread" case .croissant: return "croissant" case .baguetteBread: return "baguette bread, baguette_bread, baguettebread" @@ -720,60 +734,60 @@ extension Emoji { case .bagel: return "bagel" case .pancakes: return "pancakes" case .waffle: return "waffle" - case .cheeseWedge: return "cheesewedge, cheese wedge, cheese_wedge" - case .meatOnBone: return "meatonbone, meat on bone, meat_on_bone" - case .poultryLeg: return "poultry_leg, poultry leg, poultryleg" - case .cutOfMeat: return "cut of meat, cutofmeat, cut_of_meat" + case .cheeseWedge: return "cheese wedge, cheese_wedge, cheesewedge" + case .meatOnBone: return "meat on bone, meat_on_bone, meatonbone" + case .poultryLeg: return "poultry leg, poultry_leg, poultryleg" + case .cutOfMeat: return "cut of meat, cut_of_meat, cutofmeat" case .bacon: return "bacon" case .hamburger: return "hamburger" case .fries: return "french fries, fries" - case .pizza: return "slice of pizza, pizza" - case .hotdog: return "hotdog, hot dog" + case .pizza: return "pizza, slice of pizza" + case .hotdog: return "hot dog, hotdog" case .sandwich: return "sandwich" case .taco: return "taco" case .burrito: return "burrito" case .tamale: return "tamale" - case .stuffedFlatbread: return "stuffed_flatbread, stuffed flatbread, stuffedflatbread" + case .stuffedFlatbread: return "stuffed flatbread, stuffed_flatbread, stuffedflatbread" case .falafel: return "falafel" case .egg: return "egg" - case .friedEgg: return "friedegg, fried_egg, cooking" + case .friedEgg: return "cooking, fried_egg, friedegg" case .shallowPanOfFood: return "shallow pan of food, shallow_pan_of_food, shallowpanoffood" case .stew: return "pot of food, stew" case .fondue: return "fondue" - case .bowlWithSpoon: return "bowlwithspoon, bowl_with_spoon, bowl with spoon" - case .greenSalad: return "greensalad, green salad, green_salad" + case .bowlWithSpoon: return "bowl with spoon, bowl_with_spoon, bowlwithspoon" + case .greenSalad: return "green salad, green_salad, greensalad" case .popcorn: return "popcorn" case .butter: return "butter" - case .salt: return "salt shaker, salt" - case .cannedFood: return "canned_food, canned food, cannedfood" + case .salt: return "salt, salt shaker" + case .cannedFood: return "canned food, canned_food, cannedfood" case .bento: return "bento, bento box" - case .riceCracker: return "rice_cracker, ricecracker, rice cracker" - case .riceBall: return "rice ball, riceball, rice_ball" - case .rice: return "rice, cooked rice" + case .riceCracker: return "rice cracker, rice_cracker, ricecracker" + case .riceBall: return "rice ball, rice_ball, riceball" + case .rice: return "cooked rice, rice" case .curry: return "curry, curry and rice" - case .ramen: return "steaming bowl, ramen" + case .ramen: return "ramen, steaming bowl" case .spaghetti: return "spaghetti" - case .sweetPotato: return "sweet_potato, roasted sweet potato, sweetpotato" + case .sweetPotato: return "roasted sweet potato, sweet_potato, sweetpotato" case .oden: return "oden" case .sushi: return "sushi" - case .friedShrimp: return "fried_shrimp, friedshrimp, fried shrimp" - case .fishCake: return "fish_cake, fish cake with swirl design, fishcake" - case .moonCake: return "mooncake, moon_cake, moon cake" + case .friedShrimp: return "fried shrimp, fried_shrimp, friedshrimp" + case .fishCake: return "fish cake with swirl design, fish_cake, fishcake" + case .moonCake: return "moon cake, moon_cake, mooncake" case .dango: return "dango" case .dumpling: return "dumpling" - case .fortuneCookie: return "fortune_cookie, fortune cookie, fortunecookie" - case .takeoutBox: return "takeoutbox, takeout_box, takeout box" + case .fortuneCookie: return "fortune cookie, fortune_cookie, fortunecookie" + case .takeoutBox: return "takeout box, takeout_box, takeoutbox" case .crab: return "crab" case .lobster: return "lobster" case .shrimp: return "shrimp" case .squid: return "squid" case .oyster: return "oyster" case .icecream: return "icecream, soft ice cream" - case .shavedIce: return "shaved ice, shavedice, shaved_ice" - case .iceCream: return "ice cream, icecream, ice_cream" + case .shavedIce: return "shaved ice, shaved_ice, shavedice" + case .iceCream: return "ice cream, ice_cream, icecream" case .doughnut: return "doughnut" case .cookie: return "cookie" - case .birthday: return "birthday cake, birthday" + case .birthday: return "birthday, birthday cake" case .cake: return "cake, shortcake" case .cupcake: return "cupcake" case .pie: return "pie" @@ -781,243 +795,243 @@ extension Emoji { case .candy: return "candy" case .lollipop: return "lollipop" case .custard: return "custard" - case .honeyPot: return "honey pot, honeypot, honey_pot" - case .babyBottle: return "baby_bottle, baby bottle, babybottle" - case .glassOfMilk: return "glass of milk, glassofmilk, glass_of_milk" - case .coffee: return "hot beverage, coffee" + case .honeyPot: return "honey pot, honey_pot, honeypot" + case .babyBottle: return "baby bottle, baby_bottle, babybottle" + case .glassOfMilk: return "glass of milk, glass_of_milk, glassofmilk" + case .coffee: return "coffee, hot beverage" case .teapot: return "teapot" case .tea: return "tea, teacup without handle" - case .sake: return "sake bottle and cup, sake" - case .champagne: return "champagne, bottle with popping cork" - case .wineGlass: return "wine_glass, wine glass, wineglass" + case .sake: return "sake, sake bottle and cup" + case .champagne: return "bottle with popping cork, champagne" + case .wineGlass: return "wine glass, wine_glass, wineglass" case .cocktail: return "cocktail, cocktail glass" - case .tropicalDrink: return "tropicaldrink, tropical drink, tropical_drink" + case .tropicalDrink: return "tropical drink, tropical_drink, tropicaldrink" case .beer: return "beer, beer mug" case .beers: return "beers, clinking beer mugs" - case .clinkingGlasses: return "clinkingglasses, clinking_glasses, clinking glasses" - case .tumblerGlass: return "tumbler glass, tumblerglass, tumbler_glass" - case .pouringLiquid: return "pouring_liquid, pouring liquid, pouringliquid" - case .cupWithStraw: return "cup with straw, cupwithstraw, cup_with_straw" - case .bubbleTea: return "bubbletea, bubble_tea, bubble tea" + case .clinkingGlasses: return "clinking glasses, clinking_glasses, clinkingglasses" + case .tumblerGlass: return "tumbler glass, tumbler_glass, tumblerglass" + case .pouringLiquid: return "pouring liquid, pouring_liquid, pouringliquid" + case .cupWithStraw: return "cup with straw, cup_with_straw, cupwithstraw" + case .bubbleTea: return "bubble tea, bubble_tea, bubbletea" case .beverageBox: return "beverage box, beverage_box, beveragebox" - case .mateDrink: return "mate_drink, mate drink, matedrink" - case .iceCube: return "ice_cube, ice cube, icecube" + case .mateDrink: return "mate drink, mate_drink, matedrink" + case .iceCube: return "ice cube, ice_cube, icecube" case .chopsticks: return "chopsticks" - case .knifeForkPlate: return "fork and knife with plate, knifeforkplate, knife_fork_plate" - case .forkAndKnife: return "forkandknife, fork and knife, fork_and_knife" + case .knifeForkPlate: return "fork and knife with plate, knife_fork_plate, knifeforkplate" + case .forkAndKnife: return "fork and knife, fork_and_knife, forkandknife" case .spoon: return "spoon" case .hocho: return "hocho, knife" case .jar: return "jar" case .amphora: return "amphora" case .earthAfrica: return "earth globe europe-africa, earth_africa, earthafrica" - case .earthAmericas: return "earth globe americas, earthamericas, earth_americas" - case .earthAsia: return "earthasia, earth_asia, earth globe asia-australia" - case .globeWithMeridians: return "globewithmeridians, globe_with_meridians, globe with meridians" - case .worldMap: return "world_map, world map, worldmap" + case .earthAmericas: return "earth globe americas, earth_americas, earthamericas" + case .earthAsia: return "earth globe asia-australia, earth_asia, earthasia" + case .globeWithMeridians: return "globe with meridians, globe_with_meridians, globewithmeridians" + case .worldMap: return "world map, world_map, worldmap" case .japan: return "japan, silhouette of japan" case .compass: return "compass" - case .snowCappedMountain: return "snow_capped_mountain, snow-capped mountain, snowcappedmountain" + case .snowCappedMountain: return "snow-capped mountain, snow_capped_mountain, snowcappedmountain" case .mountain: return "mountain" case .volcano: return "volcano" - case .mountFuji: return "mount_fuji, mount fuji, mountfuji" + case .mountFuji: return "mount fuji, mount_fuji, mountfuji" case .camping: return "camping" case .beachWithUmbrella: return "beach with umbrella, beach_with_umbrella, beachwithumbrella" case .desert: return "desert" - case .desertIsland: return "desert_island, desert island, desertisland" - case .nationalPark: return "nationalpark, national_park, national park" + case .desertIsland: return "desert island, desert_island, desertisland" + case .nationalPark: return "national park, national_park, nationalpark" case .stadium: return "stadium" - case .classicalBuilding: return "classical_building, classical building, classicalbuilding" - case .buildingConstruction: return "building_construction, buildingconstruction, building construction" + case .classicalBuilding: return "classical building, classical_building, classicalbuilding" + case .buildingConstruction: return "building construction, building_construction, buildingconstruction" case .bricks: return "brick, bricks" case .rock: return "rock" case .wood: return "wood" case .hut: return "hut" - case .houseBuildings: return "housebuildings, house_buildings, houses" - case .derelictHouseBuilding: return "derelict_house_building, derelict house, derelicthousebuilding" + case .houseBuildings: return "house_buildings, housebuildings, houses" + case .derelictHouseBuilding: return "derelict house, derelict_house_building, derelicthousebuilding" case .house: return "house, house building" case .houseWithGarden: return "house with garden, house_with_garden, housewithgarden" - case .office: return "office building, office" - case .postOffice: return "post_office, japanese post office, postoffice" + case .office: return "office, office building" + case .postOffice: return "japanese post office, post_office, postoffice" case .europeanPostOffice: return "european post office, european_post_office, europeanpostoffice" case .hospital: return "hospital" case .bank: return "bank" case .hotel: return "hotel" - case .loveHotel: return "love_hotel, love hotel, lovehotel" - case .convenienceStore: return "convenience store, conveniencestore, convenience_store" + case .loveHotel: return "love hotel, love_hotel, lovehotel" + case .convenienceStore: return "convenience store, convenience_store, conveniencestore" case .school: return "school" - case .departmentStore: return "department_store, department store, departmentstore" + case .departmentStore: return "department store, department_store, departmentstore" case .factory: return "factory" - case .japaneseCastle: return "japanese_castle, japanese castle, japanesecastle" - case .europeanCastle: return "europeancastle, european_castle, european castle" + case .japaneseCastle: return "japanese castle, japanese_castle, japanesecastle" + case .europeanCastle: return "european castle, european_castle, europeancastle" case .wedding: return "wedding" - case .tokyoTower: return "tokyo tower, tokyotower, tokyo_tower" - case .statueOfLiberty: return "statue of liberty, statueofliberty, statue_of_liberty" + case .tokyoTower: return "tokyo tower, tokyo_tower, tokyotower" + case .statueOfLiberty: return "statue of liberty, statue_of_liberty, statueofliberty" case .church: return "church" case .mosque: return "mosque" case .hinduTemple: return "hindu temple, hindu_temple, hindutemple" case .synagogue: return "synagogue" - case .shintoShrine: return "shinto shrine, shintoshrine, shinto_shrine" + case .shintoShrine: return "shinto shrine, shinto_shrine, shintoshrine" case .kaaba: return "kaaba" case .fountain: return "fountain" case .tent: return "tent" case .foggy: return "foggy" - case .nightWithStars: return "night with stars, nightwithstars, night_with_stars" + case .nightWithStars: return "night with stars, night_with_stars, nightwithstars" case .cityscape: return "cityscape" - case .sunriseOverMountains: return "sunrise_over_mountains, sunrise over mountains, sunriseovermountains" + case .sunriseOverMountains: return "sunrise over mountains, sunrise_over_mountains, sunriseovermountains" case .sunrise: return "sunrise" - case .citySunset: return "cityscape at dusk, city_sunset, citysunset" - case .citySunrise: return "city_sunrise, sunset over buildings, citysunrise" + case .citySunset: return "city_sunset, cityscape at dusk, citysunset" + case .citySunrise: return "city_sunrise, citysunrise, sunset over buildings" case .bridgeAtNight: return "bridge at night, bridge_at_night, bridgeatnight" - case .hotsprings: return "hotsprings, hot springs" + case .hotsprings: return "hot springs, hotsprings" case .carouselHorse: return "carousel horse, carousel_horse, carouselhorse" - case .playgroundSlide: return "playground_slide, playground slide, playgroundslide" - case .ferrisWheel: return "ferris_wheel, ferriswheel, ferris wheel" - case .rollerCoaster: return "roller_coaster, rollercoaster, roller coaster" - case .barber: return "barber pole, barber" - case .circusTent: return "circus tent, circustent, circus_tent" - case .steamLocomotive: return "steam_locomotive, steam locomotive, steamlocomotive" - case .railwayCar: return "railwaycar, railway_car, railway car" - case .bullettrainSide: return "high-speed train, bullettrain_side, bullettrainside" - case .bullettrainFront: return "high-speed train with bullet nose, bullettrain_front, bullettrainfront" - case .train2: return "train2, train" + case .playgroundSlide: return "playground slide, playground_slide, playgroundslide" + case .ferrisWheel: return "ferris wheel, ferris_wheel, ferriswheel" + case .rollerCoaster: return "roller coaster, roller_coaster, rollercoaster" + case .barber: return "barber, barber pole" + case .circusTent: return "circus tent, circus_tent, circustent" + case .steamLocomotive: return "steam locomotive, steam_locomotive, steamlocomotive" + case .railwayCar: return "railway car, railway_car, railwaycar" + case .bullettrainSide: return "bullettrain_side, bullettrainside, high-speed train" + case .bullettrainFront: return "bullettrain_front, bullettrainfront, high-speed train with bullet nose" + case .train2: return "train, train2" case .metro: return "metro" case .lightRail: return "light rail, light_rail, lightrail" case .station: return "station" case .tram: return "tram" case .monorail: return "monorail" - case .mountainRailway: return "mountain railway, mountainrailway, mountain_railway" + case .mountainRailway: return "mountain railway, mountain_railway, mountainrailway" case .train: return "train, tram car" case .bus: return "bus" - case .oncomingBus: return "oncoming bus, oncomingbus, oncoming_bus" + case .oncomingBus: return "oncoming bus, oncoming_bus, oncomingbus" case .trolleybus: return "trolleybus" case .minibus: return "minibus" case .ambulance: return "ambulance" - case .fireEngine: return "fire_engine, fire engine, fireengine" - case .policeCar: return "police_car, policecar, police car" - case .oncomingPoliceCar: return "oncoming_police_car, oncomingpolicecar, oncoming police car" + case .fireEngine: return "fire engine, fire_engine, fireengine" + case .policeCar: return "police car, police_car, policecar" + case .oncomingPoliceCar: return "oncoming police car, oncoming_police_car, oncomingpolicecar" case .taxi: return "taxi" - case .oncomingTaxi: return "oncoming_taxi, oncoming taxi, oncomingtaxi" - case .car: return "car, red_car, automobile" + case .oncomingTaxi: return "oncoming taxi, oncoming_taxi, oncomingtaxi" + case .car: return "automobile, car, red_car" case .oncomingAutomobile: return "oncoming automobile, oncoming_automobile, oncomingautomobile" - case .blueCar: return "bluecar, blue_car, recreational vehicle" - case .pickupTruck: return "pickup_truck, pickup truck, pickuptruck" + case .blueCar: return "blue_car, bluecar, recreational vehicle" + case .pickupTruck: return "pickup truck, pickup_truck, pickuptruck" case .truck: return "delivery truck, truck" - case .articulatedLorry: return "articulated_lorry, articulated lorry, articulatedlorry" + case .articulatedLorry: return "articulated lorry, articulated_lorry, articulatedlorry" case .tractor: return "tractor" - case .racingCar: return "racing car, racingcar, racing_car" - case .racingMotorcycle: return "racing_motorcycle, motorcycle, racingmotorcycle" + case .racingCar: return "racing car, racing_car, racingcar" + case .racingMotorcycle: return "motorcycle, racing_motorcycle, racingmotorcycle" case .motorScooter: return "motor scooter, motor_scooter, motorscooter" - case .manualWheelchair: return "manual_wheelchair, manualwheelchair, manual wheelchair" - case .motorizedWheelchair: return "motorized_wheelchair, motorized wheelchair, motorizedwheelchair" + case .manualWheelchair: return "manual wheelchair, manual_wheelchair, manualwheelchair" + case .motorizedWheelchair: return "motorized wheelchair, motorized_wheelchair, motorizedwheelchair" case .autoRickshaw: return "auto rickshaw, auto_rickshaw, autorickshaw" case .bike: return "bicycle, bike" case .scooter: return "scooter" case .skateboard: return "skateboard" - case .rollerSkate: return "roller skate, rollerskate, roller_skate" + case .rollerSkate: return "roller skate, roller_skate, rollerskate" case .busstop: return "bus stop, busstop" case .motorway: return "motorway" - case .railwayTrack: return "railwaytrack, railway track, railway_track" - case .oilDrum: return "oil_drum, oil drum, oildrum" - case .fuelpump: return "fuelpump, fuel pump" + case .railwayTrack: return "railway track, railway_track, railwaytrack" + case .oilDrum: return "oil drum, oil_drum, oildrum" + case .fuelpump: return "fuel pump, fuelpump" case .wheel: return "wheel" case .rotatingLight: return "police cars revolving light, rotating_light, rotatinglight" - case .trafficLight: return "horizontal traffic light, trafficlight, traffic_light" - case .verticalTrafficLight: return "verticaltrafficlight, vertical traffic light, vertical_traffic_light" - case .octagonalSign: return "octagonal_sign, octagonalsign, octagonal sign" - case .construction: return "construction sign, construction" + case .trafficLight: return "horizontal traffic light, traffic_light, trafficlight" + case .verticalTrafficLight: return "vertical traffic light, vertical_traffic_light, verticaltrafficlight" + case .octagonalSign: return "octagonal sign, octagonal_sign, octagonalsign" + case .construction: return "construction, construction sign" case .anchor: return "anchor" - case .ringBuoy: return "ring_buoy, ringbuoy, ring buoy" - case .boat: return "sailboat, boat" + case .ringBuoy: return "ring buoy, ring_buoy, ringbuoy" + case .boat: return "boat, sailboat" case .canoe: return "canoe" case .speedboat: return "speedboat" - case .passengerShip: return "passenger_ship, passenger ship, passengership" + case .passengerShip: return "passenger ship, passenger_ship, passengership" case .ferry: return "ferry" - case .motorBoat: return "motorboat, motor_boat, motor boat" + case .motorBoat: return "motor boat, motor_boat, motorboat" case .ship: return "ship" case .airplane: return "airplane" - case .smallAirplane: return "small_airplane, smallairplane, small airplane" + case .smallAirplane: return "small airplane, small_airplane, smallairplane" case .airplaneDeparture: return "airplane departure, airplane_departure, airplanedeparture" case .airplaneArriving: return "airplane arriving, airplane_arriving, airplanearriving" case .parachute: return "parachute" case .seat: return "seat" case .helicopter: return "helicopter" - case .suspensionRailway: return "suspension_railway, suspension railway, suspensionrailway" - case .mountainCableway: return "mountain_cableway, mountain cableway, mountaincableway" - case .aerialTramway: return "aerial tramway, aerialtramway, aerial_tramway" + case .suspensionRailway: return "suspension railway, suspension_railway, suspensionrailway" + case .mountainCableway: return "mountain cableway, mountain_cableway, mountaincableway" + case .aerialTramway: return "aerial tramway, aerial_tramway, aerialtramway" case .satellite: return "satellite" case .rocket: return "rocket" - case .flyingSaucer: return "flying saucer, flyingsaucer, flying_saucer" - case .bellhopBell: return "bellhop_bell, bellhop bell, bellhopbell" + case .flyingSaucer: return "flying saucer, flying_saucer, flyingsaucer" + case .bellhopBell: return "bellhop bell, bellhop_bell, bellhopbell" case .luggage: return "luggage" case .hourglass: return "hourglass" case .hourglassFlowingSand: return "hourglass with flowing sand, hourglass_flowing_sand, hourglassflowingsand" case .watch: return "watch" - case .alarmClock: return "alarm_clock, alarmclock, alarm clock" + case .alarmClock: return "alarm clock, alarm_clock, alarmclock" case .stopwatch: return "stopwatch" - case .timerClock: return "timer_clock, timerclock, timer clock" - case .mantelpieceClock: return "mantelpiece_clock, mantelpiece clock, mantelpiececlock" - case .clock12: return "clock12, clock face twelve oclock" + case .timerClock: return "timer clock, timer_clock, timerclock" + case .mantelpieceClock: return "mantelpiece clock, mantelpiece_clock, mantelpiececlock" + case .clock12: return "clock face twelve oclock, clock12" case .clock1230: return "clock face twelve-thirty, clock1230" case .clock1: return "clock face one oclock, clock1" - case .clock130: return "clock130, clock face one-thirty" + case .clock130: return "clock face one-thirty, clock130" case .clock2: return "clock face two oclock, clock2" - case .clock230: return "clock230, clock face two-thirty" - case .clock3: return "clock3, clock face three oclock" - case .clock330: return "clock330, clock face three-thirty" + case .clock230: return "clock face two-thirty, clock230" + case .clock3: return "clock face three oclock, clock3" + case .clock330: return "clock face three-thirty, clock330" case .clock4: return "clock face four oclock, clock4" - case .clock430: return "clock430, clock face four-thirty" + case .clock430: return "clock face four-thirty, clock430" case .clock5: return "clock face five oclock, clock5" - case .clock530: return "clock530, clock face five-thirty" - case .clock6: return "clock6, clock face six oclock" + case .clock530: return "clock face five-thirty, clock530" + case .clock6: return "clock face six oclock, clock6" case .clock630: return "clock face six-thirty, clock630" case .clock7: return "clock face seven oclock, clock7" case .clock730: return "clock face seven-thirty, clock730" case .clock8: return "clock face eight oclock, clock8" case .clock830: return "clock face eight-thirty, clock830" case .clock9: return "clock face nine oclock, clock9" - case .clock930: return "clock930, clock face nine-thirty" - case .clock10: return "clock10, clock face ten oclock" - case .clock1030: return "clock1030, clock face ten-thirty" + case .clock930: return "clock face nine-thirty, clock930" + case .clock10: return "clock face ten oclock, clock10" + case .clock1030: return "clock face ten-thirty, clock1030" case .clock11: return "clock face eleven oclock, clock11" - case .clock1130: return "clock1130, clock face eleven-thirty" - case .newMoon: return "new moon symbol, newmoon, new_moon" + case .clock1130: return "clock face eleven-thirty, clock1130" + case .newMoon: return "new moon symbol, new_moon, newmoon" case .waxingCrescentMoon: return "waxing crescent moon symbol, waxing_crescent_moon, waxingcrescentmoon" - case .firstQuarterMoon: return "firstquartermoon, first_quarter_moon, first quarter moon symbol" - case .moon: return "waxing gibbous moon symbol, waxing_gibbous_moon, moon" - case .fullMoon: return "full_moon, full moon symbol, fullmoon" - case .waningGibbousMoon: return "waning_gibbous_moon, waning gibbous moon symbol, waninggibbousmoon" - case .lastQuarterMoon: return "lastquartermoon, last_quarter_moon, last quarter moon symbol" - case .waningCrescentMoon: return "waning_crescent_moon, waning crescent moon symbol, waningcrescentmoon" - case .crescentMoon: return "crescent_moon, crescent moon, crescentmoon" - case .newMoonWithFace: return "newmoonwithface, new_moon_with_face, new moon with face" + case .firstQuarterMoon: return "first quarter moon symbol, first_quarter_moon, firstquartermoon" + case .moon: return "moon, waxing gibbous moon symbol, waxing_gibbous_moon" + case .fullMoon: return "full moon symbol, full_moon, fullmoon" + case .waningGibbousMoon: return "waning gibbous moon symbol, waning_gibbous_moon, waninggibbousmoon" + case .lastQuarterMoon: return "last quarter moon symbol, last_quarter_moon, lastquartermoon" + case .waningCrescentMoon: return "waning crescent moon symbol, waning_crescent_moon, waningcrescentmoon" + case .crescentMoon: return "crescent moon, crescent_moon, crescentmoon" + case .newMoonWithFace: return "new moon with face, new_moon_with_face, newmoonwithface" case .firstQuarterMoonWithFace: return "first quarter moon with face, first_quarter_moon_with_face, firstquartermoonwithface" - case .lastQuarterMoonWithFace: return "lastquartermoonwithface, last_quarter_moon_with_face, last quarter moon with face" + case .lastQuarterMoonWithFace: return "last quarter moon with face, last_quarter_moon_with_face, lastquartermoonwithface" case .thermometer: return "thermometer" case .sunny: return "black sun with rays, sunny" - case .fullMoonWithFace: return "full_moon_with_face, full moon with face, fullmoonwithface" - case .sunWithFace: return "sun_with_face, sunwithface, sun with face" - case .ringedPlanet: return "ringed_planet, ringed planet, ringedplanet" + case .fullMoonWithFace: return "full moon with face, full_moon_with_face, fullmoonwithface" + case .sunWithFace: return "sun with face, sun_with_face, sunwithface" + case .ringedPlanet: return "ringed planet, ringed_planet, ringedplanet" case .star: return "star, white medium star" - case .star2: return "star2, glowing star" + case .star2: return "glowing star, star2" case .stars: return "shooting star, stars" case .milkyWay: return "milky way, milky_way, milkyway" case .cloud: return "cloud" - case .partlySunny: return "partly_sunny, sun behind cloud, partlysunny" - case .thunderCloudAndRain: return "thunder_cloud_and_rain, cloud with lightning and rain, thundercloudandrain" - case .mostlySunny: return "sun_small_cloud, mostlysunny, sun behind small cloud, mostly_sunny" - case .barelySunny: return "sun_behind_cloud, barely_sunny, sun behind large cloud, barelysunny" - case .partlySunnyRain: return "sun behind rain cloud, partly_sunny_rain, partlysunnyrain, sun_behind_rain_cloud" - case .rainCloud: return "cloud with rain, raincloud, rain_cloud" - case .snowCloud: return "snow_cloud, snowcloud, cloud with snow" + case .partlySunny: return "partly_sunny, partlysunny, sun behind cloud" + case .thunderCloudAndRain: return "cloud with lightning and rain, thunder_cloud_and_rain, thundercloudandrain" + case .mostlySunny: return "mostly_sunny, mostlysunny, sun behind small cloud, sun_small_cloud" + case .barelySunny: return "barely_sunny, barelysunny, sun behind large cloud, sun_behind_cloud" + case .partlySunnyRain: return "partly_sunny_rain, partlysunnyrain, sun behind rain cloud, sun_behind_rain_cloud" + case .rainCloud: return "cloud with rain, rain_cloud, raincloud" + case .snowCloud: return "cloud with snow, snow_cloud, snowcloud" case .lightning: return "cloud with lightning, lightning, lightning_cloud" case .tornado: return "tornado, tornado_cloud" case .fog: return "fog" case .windBlowingFace: return "wind face, wind_blowing_face, windblowingface" case .cyclone: return "cyclone" case .rainbow: return "rainbow" - case .closedUmbrella: return "closed_umbrella, closedumbrella, closed umbrella" + case .closedUmbrella: return "closed umbrella, closed_umbrella, closedumbrella" case .umbrella: return "umbrella" - case .umbrellaWithRainDrops: return "umbrella with rain drops, umbrellawithraindrops, umbrella_with_rain_drops" + case .umbrellaWithRainDrops: return "umbrella with rain drops, umbrella_with_rain_drops, umbrellawithraindrops" case .umbrellaOnGround: return "umbrella on ground, umbrella_on_ground, umbrellaonground" case .zap: return "high voltage sign, zap" case .snowflake: return "snowflake" @@ -1028,98 +1042,97 @@ extension Emoji { case .droplet: return "droplet" case .ocean: return "ocean, water wave" case .jackOLantern: return "jack-o-lantern, jack_o_lantern, jackolantern" - case .christmasTree: return "christmastree, christmas_tree, christmas tree" + case .christmasTree: return "christmas tree, christmas_tree, christmastree" case .fireworks: return "fireworks" - case .sparkler: return "sparkler, firework sparkler" + case .sparkler: return "firework sparkler, sparkler" case .firecracker: return "firecracker" case .sparkles: return "sparkles" case .balloon: return "balloon" - case .tada: return "tada, party popper" - case .confettiBall: return "confetti_ball, confettiball, confetti ball" - case .tanabataTree: return "tanabatatree, tanabata tree, tanabata_tree" + case .tada: return "party popper, tada" + case .confettiBall: return "confetti ball, confetti_ball, confettiball" + case .tanabataTree: return "tanabata tree, tanabata_tree, tanabatatree" case .bamboo: return "bamboo, pine decoration" case .dolls: return "dolls, japanese dolls" - case .flags: return "flags, carp streamer" - case .windChime: return "windchime, wind_chime, wind chime" + case .flags: return "carp streamer, flags" + case .windChime: return "wind chime, wind_chime, windchime" case .riceScene: return "moon viewing ceremony, rice_scene, ricescene" - case .redEnvelope: return "red gift envelope, redenvelope, red_envelope" + case .redEnvelope: return "red gift envelope, red_envelope, redenvelope" case .ribbon: return "ribbon" case .gift: return "gift, wrapped present" - case .reminderRibbon: return "reminder ribbon, reminderribbon, reminder_ribbon" - case .admissionTickets: return "admission_tickets, admission tickets, admissiontickets" + case .reminderRibbon: return "reminder ribbon, reminder_ribbon, reminderribbon" + case .admissionTickets: return "admission tickets, admission_tickets, admissiontickets" case .ticket: return "ticket" case .medal: return "medal, military medal" case .trophy: return "trophy" - case .sportsMedal: return "sportsmedal, sports medal, sports_medal" - case .firstPlaceMedal: return "first place medal, firstplacemedal, first_place_medal" - case .secondPlaceMedal: return "secondplacemedal, second_place_medal, second place medal" - case .thirdPlaceMedal: return "third_place_medal, thirdplacemedal, third place medal" - case .soccer: return "soccer ball, soccer" + case .sportsMedal: return "sports medal, sports_medal, sportsmedal" + case .firstPlaceMedal: return "first place medal, first_place_medal, firstplacemedal" + case .secondPlaceMedal: return "second place medal, second_place_medal, secondplacemedal" + case .thirdPlaceMedal: return "third place medal, third_place_medal, thirdplacemedal" + case .soccer: return "soccer, soccer ball" case .baseball: return "baseball" case .softball: return "softball" - case .basketball: return "basketball and hoop, basketball" + case .basketball: return "basketball, basketball and hoop" case .volleyball: return "volleyball" - case .football: return "football, american football" - case .rugbyFootball: return "rugby_football, rugby football, rugbyfootball" + case .football: return "american football, football" + case .rugbyFootball: return "rugby football, rugby_football, rugbyfootball" case .tennis: return "tennis, tennis racquet and ball" - case .flyingDisc: return "flying_disc, flyingdisc, flying disc" + case .flyingDisc: return "flying disc, flying_disc, flyingdisc" case .bowling: return "bowling" - case .cricketBatAndBall: return "cricket_bat_and_ball, cricketbatandball, cricket bat and ball" - case .fieldHockeyStickAndBall: return "field_hockey_stick_and_ball, field hockey stick and ball, fieldhockeystickandball" - case .iceHockeyStickAndPuck: return "ice_hockey_stick_and_puck, ice hockey stick and puck, icehockeystickandpuck" - case .lacrosse: return "lacrosse stick and ball, lacrosse" + case .cricketBatAndBall: return "cricket bat and ball, cricket_bat_and_ball, cricketbatandball" + case .fieldHockeyStickAndBall: return "field hockey stick and ball, field_hockey_stick_and_ball, fieldhockeystickandball" + case .iceHockeyStickAndPuck: return "ice hockey stick and puck, ice_hockey_stick_and_puck, icehockeystickandpuck" + case .lacrosse: return "lacrosse, lacrosse stick and ball" case .tableTennisPaddleAndBall: return "table tennis paddle and ball, table_tennis_paddle_and_ball, tabletennispaddleandball" - case .badmintonRacquetAndShuttlecock: return "badminton_racquet_and_shuttlecock, badminton racquet and shuttlecock, badmintonracquetandshuttlecock" - case .boxingGlove: return "boxing_glove, boxing glove, boxingglove" - case .martialArtsUniform: return "martial_arts_uniform, martial arts uniform, martialartsuniform" - case .goalNet: return "goalnet, goal net, goal_net" - case .golf: return "golf, flag in hole" - case .iceSkate: return "ice skate, iceskate, ice_skate" - case .fishingPoleAndFish: return "fishing pole and fish, fishingpoleandfish, fishing_pole_and_fish" - case .divingMask: return "divingmask, diving_mask, diving mask" + case .badmintonRacquetAndShuttlecock: return "badminton racquet and shuttlecock, badminton_racquet_and_shuttlecock, badmintonracquetandshuttlecock" + case .boxingGlove: return "boxing glove, boxing_glove, boxingglove" + case .martialArtsUniform: return "martial arts uniform, martial_arts_uniform, martialartsuniform" + case .goalNet: return "goal net, goal_net, goalnet" + case .golf: return "flag in hole, golf" + case .iceSkate: return "ice skate, ice_skate, iceskate" + case .fishingPoleAndFish: return "fishing pole and fish, fishing_pole_and_fish, fishingpoleandfish" + case .divingMask: return "diving mask, diving_mask, divingmask" case .runningShirtWithSash: return "running shirt with sash, running_shirt_with_sash, runningshirtwithsash" case .ski: return "ski, ski and ski boot" case .sled: return "sled" - case .curlingStone: return "curling_stone, curling stone, curlingstone" + case .curlingStone: return "curling stone, curling_stone, curlingstone" case .dart: return "dart, direct hit" case .yoYo: return "yo-yo, yoyo" case .kite: return "kite" + case .gun: return "gun, pistol" case .eightBall: return "8ball, billiards, eightball" - case .crystalBall: return "crystal ball, crystalball, crystal_ball" - case .magicWand: return "magic wand, magicwand, magic_wand" - case .nazarAmulet: return "nazar amulet, nazaramulet, nazar_amulet" - case .hamsa: return "hamsa" - case .videoGame: return "video_game, video game, videogame" + case .crystalBall: return "crystal ball, crystal_ball, crystalball" + case .magicWand: return "magic wand, magic_wand, magicwand" + case .videoGame: return "video game, video_game, videogame" case .joystick: return "joystick" - case .slotMachine: return "slotmachine, slot_machine, slot machine" - case .gameDie: return "gamedie, game die, game_die" + case .slotMachine: return "slot machine, slot_machine, slotmachine" + case .gameDie: return "game die, game_die, gamedie" case .jigsaw: return "jigsaw, jigsaw puzzle piece" - case .teddyBear: return "teddy_bear, teddy bear, teddybear" + case .teddyBear: return "teddy bear, teddy_bear, teddybear" case .pinata: return "pinata" - case .mirrorBall: return "mirrorball, mirror ball, mirror_ball" - case .nestingDolls: return "nesting dolls, nestingdolls, nesting_dolls" + case .mirrorBall: return "mirror ball, mirror_ball, mirrorball" + case .nestingDolls: return "nesting dolls, nesting_dolls, nestingdolls" case .spades: return "black spade suit, spades" case .hearts: return "black heart suit, hearts" - case .diamonds: return "diamonds, black diamond suit" - case .clubs: return "clubs, black club suit" - case .chessPawn: return "chess_pawn, chess pawn, chesspawn" + case .diamonds: return "black diamond suit, diamonds" + case .clubs: return "black club suit, clubs" + case .chessPawn: return "chess pawn, chess_pawn, chesspawn" case .blackJoker: return "black_joker, blackjoker, playing card black joker" case .mahjong: return "mahjong, mahjong tile red dragon" - case .flowerPlayingCards: return "flower playing cards, flowerplayingcards, flower_playing_cards" - case .performingArts: return "performingarts, performing_arts, performing arts" - case .frameWithPicture: return "framed picture, framewithpicture, frame_with_picture" + case .flowerPlayingCards: return "flower playing cards, flower_playing_cards, flowerplayingcards" + case .performingArts: return "performing arts, performing_arts, performingarts" + case .frameWithPicture: return "frame_with_picture, framed picture, framewithpicture" case .art: return "art, artist palette" - case .thread: return "thread, spool of thread" - case .sewingNeedle: return "sewing needle, sewingneedle, sewing_needle" + case .thread: return "spool of thread, thread" + case .sewingNeedle: return "sewing needle, sewing_needle, sewingneedle" case .yarn: return "ball of yarn, yarn" case .knot: return "knot" case .eyeglasses: return "eyeglasses" - case .darkSunglasses: return "sunglasses, darksunglasses, dark_sunglasses" + case .darkSunglasses: return "dark_sunglasses, darksunglasses, sunglasses" case .goggles: return "goggles" - case .labCoat: return "lab_coat, lab coat, labcoat" - case .safetyVest: return "safety_vest, safetyvest, safety vest" + case .labCoat: return "lab coat, lab_coat, labcoat" + case .safetyVest: return "safety vest, safety_vest, safetyvest" case .necktie: return "necktie" - case .shirt: return "t-shirt, shirt, tshirt" + case .shirt: return "shirt, t-shirt, tshirt" case .jeans: return "jeans" case .scarf: return "scarf" case .gloves: return "gloves" @@ -1128,31 +1141,33 @@ extension Emoji { case .dress: return "dress" case .kimono: return "kimono" case .sari: return "sari" - case .onePieceSwimsuit: return "one-piece swimsuit, onepieceswimsuit, one-piece_swimsuit" + case .onePieceSwimsuit: return "one-piece swimsuit, one-piece_swimsuit, onepieceswimsuit" case .briefs: return "briefs" case .shorts: return "shorts" case .bikini: return "bikini" - case .womansClothes: return "womans_clothes, womansclothes, womans clothes" + case .womansClothes: return "womans clothes, womans_clothes, womansclothes" + case .foldingHandFan: return "folding hand fan, folding_hand_fan, foldinghandfan" case .purse: return "purse" case .handbag: return "handbag" case .pouch: return "pouch" - case .shoppingBags: return "shopping bags, shoppingbags, shopping_bags" - case .schoolSatchel: return "school satchel, schoolsatchel, school_satchel" - case .thongSandal: return "thong_sandal, thongsandal, thong sandal" - case .mansShoe: return "mans_shoe, shoe, mans shoe, mansshoe" - case .athleticShoe: return "athletic_shoe, athletic shoe, athleticshoe" - case .hikingBoot: return "hikingboot, hiking boot, hiking_boot" - case .womansFlatShoe: return "flat shoe, womansflatshoe, womans_flat_shoe" + case .shoppingBags: return "shopping bags, shopping_bags, shoppingbags" + case .schoolSatchel: return "school satchel, school_satchel, schoolsatchel" + case .thongSandal: return "thong sandal, thong_sandal, thongsandal" + case .mansShoe: return "mans shoe, mans_shoe, mansshoe, shoe" + case .athleticShoe: return "athletic shoe, athletic_shoe, athleticshoe" + case .hikingBoot: return "hiking boot, hiking_boot, hikingboot" + case .womansFlatShoe: return "flat shoe, womans_flat_shoe, womansflatshoe" case .highHeel: return "high-heeled shoe, high_heel, highheel" case .sandal: return "sandal, womans sandal" - case .balletShoes: return "balletshoes, ballet_shoes, ballet shoes" + case .balletShoes: return "ballet shoes, ballet_shoes, balletshoes" case .boot: return "boot, womans boots" + case .hairPick: return "hair pick, hair_pick, hairpick" case .crown: return "crown" - case .womansHat: return "womans_hat, womanshat, womans hat" - case .tophat: return "tophat, top hat" - case .mortarBoard: return "mortarboard, mortar_board, graduation cap" - case .billedCap: return "billed_cap, billed cap, billedcap" - case .militaryHelmet: return "militaryhelmet, military helmet, military_helmet" + case .womansHat: return "womans hat, womans_hat, womanshat" + case .tophat: return "top hat, tophat" + case .mortarBoard: return "graduation cap, mortar_board, mortarboard" + case .billedCap: return "billed cap, billed_cap, billedcap" + case .militaryHelmet: return "military helmet, military_helmet, militaryhelmet" case .helmetWithWhiteCross: return "helmet_with_white_cross, helmetwithwhitecross, rescue worker’s helmet" case .prayerBeads: return "prayer beads, prayer_beads, prayerbeads" case .lipstick: return "lipstick" @@ -1161,157 +1176,159 @@ extension Emoji { case .mute: return "mute, speaker with cancellation stroke" case .speaker: return "speaker" case .sound: return "sound, speaker with one sound wave" - case .loudSound: return "loud_sound, speaker with three sound waves, loudsound" - case .loudspeaker: return "public address loudspeaker, loudspeaker" - case .mega: return "mega, cheering megaphone" + case .loudSound: return "loud_sound, loudsound, speaker with three sound waves" + case .loudspeaker: return "loudspeaker, public address loudspeaker" + case .mega: return "cheering megaphone, mega" case .postalHorn: return "postal horn, postal_horn, postalhorn" case .bell: return "bell" - case .noBell: return "nobell, no_bell, bell with cancellation stroke" - case .musicalScore: return "musical_score, musicalscore, musical score" - case .musicalNote: return "musical_note, musical note, musicalnote" + case .noBell: return "bell with cancellation stroke, no_bell, nobell" + case .musicalScore: return "musical score, musical_score, musicalscore" + case .musicalNote: return "musical note, musical_note, musicalnote" case .notes: return "multiple musical notes, notes" case .studioMicrophone: return "studio microphone, studio_microphone, studiomicrophone" - case .levelSlider: return "levelslider, level slider, level_slider" - case .controlKnobs: return "control_knobs, control knobs, controlknobs" + case .levelSlider: return "level slider, level_slider, levelslider" + case .controlKnobs: return "control knobs, control_knobs, controlknobs" case .microphone: return "microphone" - case .headphones: return "headphones, headphone" + case .headphones: return "headphone, headphones" case .radio: return "radio" case .saxophone: return "saxophone" case .accordion: return "accordion" case .guitar: return "guitar" - case .musicalKeyboard: return "musicalkeyboard, musical keyboard, musical_keyboard" + case .musicalKeyboard: return "musical keyboard, musical_keyboard, musicalkeyboard" case .trumpet: return "trumpet" case .violin: return "violin" case .banjo: return "banjo" - case .drumWithDrumsticks: return "drum_with_drumsticks, drum with drumsticks, drumwithdrumsticks" + case .drumWithDrumsticks: return "drum with drumsticks, drum_with_drumsticks, drumwithdrumsticks" case .longDrum: return "long drum, long_drum, longdrum" + case .maracas: return "maracas" + case .flute: return "flute" case .iphone: return "iphone, mobile phone" case .calling: return "calling, mobile phone with rightwards arrow at left" case .phone: return "black telephone, phone, telephone" - case .telephoneReceiver: return "telephone receiver, telephonereceiver, telephone_receiver" + case .telephoneReceiver: return "telephone receiver, telephone_receiver, telephonereceiver" case .pager: return "pager" - case .fax: return "fax machine, fax" + case .fax: return "fax, fax machine" case .battery: return "battery" - case .lowBattery: return "lowbattery, low_battery, low battery" - case .electricPlug: return "electricplug, electric plug, electric_plug" - case .computer: return "personal computer, computer" - case .desktopComputer: return "desktop_computer, desktop computer, desktopcomputer" + case .lowBattery: return "low battery, low_battery, lowbattery" + case .electricPlug: return "electric plug, electric_plug, electricplug" + case .computer: return "computer, personal computer" + case .desktopComputer: return "desktop computer, desktop_computer, desktopcomputer" case .printer: return "printer" case .keyboard: return "keyboard" - case .threeButtonMouse: return "computer mouse, threebuttonmouse, three_button_mouse" + case .threeButtonMouse: return "computer mouse, three_button_mouse, threebuttonmouse" case .trackball: return "trackball" case .minidisc: return "minidisc" - case .floppyDisk: return "floppydisk, floppy_disk, floppy disk" - case .cd: return "optical disc, cd" + case .floppyDisk: return "floppy disk, floppy_disk, floppydisk" + case .cd: return "cd, optical disc" case .dvd: return "dvd" case .abacus: return "abacus" - case .movieCamera: return "movie camera, moviecamera, movie_camera" - case .filmFrames: return "filmframes, film frames, film_frames" - case .filmProjector: return "film_projector, filmprojector, film projector" + case .movieCamera: return "movie camera, movie_camera, moviecamera" + case .filmFrames: return "film frames, film_frames, filmframes" + case .filmProjector: return "film projector, film_projector, filmprojector" case .clapper: return "clapper, clapper board" case .tv: return "television, tv" case .camera: return "camera" - case .cameraWithFlash: return "camera with flash, camerawithflash, camera_with_flash" - case .videoCamera: return "video_camera, video camera, videocamera" + case .cameraWithFlash: return "camera with flash, camera_with_flash, camerawithflash" + case .videoCamera: return "video camera, video_camera, videocamera" case .vhs: return "vhs, videocassette" - case .mag: return "mag, left-pointing magnifying glass" + case .mag: return "left-pointing magnifying glass, mag" case .magRight: return "mag_right, magright, right-pointing magnifying glass" case .candle: return "candle" - case .bulb: return "electric light bulb, bulb" + case .bulb: return "bulb, electric light bulb" case .flashlight: return "electric torch, flashlight" - case .izakayaLantern: return "izakaya_lantern, izakaya lantern, lantern, izakayalantern" - case .diyaLamp: return "diyalamp, diya_lamp, diya lamp" - case .notebookWithDecorativeCover: return "notebook_with_decorative_cover, notebook with decorative cover, notebookwithdecorativecover" - case .closedBook: return "closed_book, closed book, closedbook" - case .book: return "book, open_book, open book" - case .greenBook: return "greenbook, green book, green_book" - case .blueBook: return "blue_book, bluebook, blue book" - case .orangeBook: return "orange book, orangebook, orange_book" + case .izakayaLantern: return "izakaya lantern, izakaya_lantern, izakayalantern, lantern" + case .diyaLamp: return "diya lamp, diya_lamp, diyalamp" + case .notebookWithDecorativeCover: return "notebook with decorative cover, notebook_with_decorative_cover, notebookwithdecorativecover" + case .closedBook: return "closed book, closed_book, closedbook" + case .book: return "book, open book, open_book" + case .greenBook: return "green book, green_book, greenbook" + case .blueBook: return "blue book, blue_book, bluebook" + case .orangeBook: return "orange book, orange_book, orangebook" case .books: return "books" case .notebook: return "notebook" case .ledger: return "ledger" case .pageWithCurl: return "page with curl, page_with_curl, pagewithcurl" case .scroll: return "scroll" - case .pageFacingUp: return "pagefacingup, page_facing_up, page facing up" + case .pageFacingUp: return "page facing up, page_facing_up, pagefacingup" case .newspaper: return "newspaper" - case .rolledUpNewspaper: return "rolledupnewspaper, rolled_up_newspaper, rolled-up newspaper" - case .bookmarkTabs: return "bookmarktabs, bookmark_tabs, bookmark tabs" + case .rolledUpNewspaper: return "rolled-up newspaper, rolled_up_newspaper, rolledupnewspaper" + case .bookmarkTabs: return "bookmark tabs, bookmark_tabs, bookmarktabs" case .bookmark: return "bookmark" case .label: return "label" case .moneybag: return "money bag, moneybag" case .coin: return "coin" case .yen: return "banknote with yen sign, yen" - case .dollar: return "dollar, banknote with dollar sign" + case .dollar: return "banknote with dollar sign, dollar" case .euro: return "banknote with euro sign, euro" - case .pound: return "pound, banknote with pound sign" - case .moneyWithWings: return "money_with_wings, money with wings, moneywithwings" - case .creditCard: return "creditcard, credit card, credit_card" + case .pound: return "banknote with pound sign, pound" + case .moneyWithWings: return "money with wings, money_with_wings, moneywithwings" + case .creditCard: return "credit card, credit_card, creditcard" case .receipt: return "receipt" case .chart: return "chart, chart with upwards trend and yen sign" case .email: return "email, envelope" - case .eMail: return "email, e-mail, e-mail symbol" - case .incomingEnvelope: return "incomingenvelope, incoming_envelope, incoming envelope" - case .envelopeWithArrow: return "envelope with downwards arrow above, envelopewitharrow, envelope_with_arrow" - case .outboxTray: return "outbox tray, outboxtray, outbox_tray" - case .inboxTray: return "inboxtray, inbox_tray, inbox tray" + case .eMail: return "e-mail, e-mail symbol, email" + case .incomingEnvelope: return "incoming envelope, incoming_envelope, incomingenvelope" + case .envelopeWithArrow: return "envelope with downwards arrow above, envelope_with_arrow, envelopewitharrow" + case .outboxTray: return "outbox tray, outbox_tray, outboxtray" + case .inboxTray: return "inbox tray, inbox_tray, inboxtray" case .package: return "package" case .mailbox: return "closed mailbox with raised flag, mailbox" - case .mailboxClosed: return "mailbox_closed, closed mailbox with lowered flag, mailboxclosed" - case .mailboxWithMail: return "mailboxwithmail, mailbox_with_mail, open mailbox with raised flag" - case .mailboxWithNoMail: return "open mailbox with lowered flag, mailboxwithnomail, mailbox_with_no_mail" + case .mailboxClosed: return "closed mailbox with lowered flag, mailbox_closed, mailboxclosed" + case .mailboxWithMail: return "mailbox_with_mail, mailboxwithmail, open mailbox with raised flag" + case .mailboxWithNoMail: return "mailbox_with_no_mail, mailboxwithnomail, open mailbox with lowered flag" case .postbox: return "postbox" - case .ballotBoxWithBallot: return "ballotboxwithballot, ballot box with ballot, ballot_box_with_ballot" - case .pencil2: return "pencil2, pencil" + case .ballotBoxWithBallot: return "ballot box with ballot, ballot_box_with_ballot, ballotboxwithballot" + case .pencil2: return "pencil, pencil2" case .blackNib: return "black nib, black_nib, blacknib" - case .lowerLeftFountainPen: return "lowerleftfountainpen, lower_left_fountain_pen, fountain pen" - case .lowerLeftBallpointPen: return "pen, lowerleftballpointpen, lower_left_ballpoint_pen" - case .lowerLeftPaintbrush: return "lowerleftpaintbrush, paintbrush, lower_left_paintbrush" - case .lowerLeftCrayon: return "crayon, lowerleftcrayon, lower_left_crayon" + case .lowerLeftFountainPen: return "fountain pen, lower_left_fountain_pen, lowerleftfountainpen" + case .lowerLeftBallpointPen: return "lower_left_ballpoint_pen, lowerleftballpointpen, pen" + case .lowerLeftPaintbrush: return "lower_left_paintbrush, lowerleftpaintbrush, paintbrush" + case .lowerLeftCrayon: return "crayon, lower_left_crayon, lowerleftcrayon" case .memo: return "memo, pencil" case .briefcase: return "briefcase" - case .fileFolder: return "filefolder, file folder, file_folder" - case .openFileFolder: return "openfilefolder, open file folder, open_file_folder" - case .cardIndexDividers: return "card index dividers, cardindexdividers, card_index_dividers" + case .fileFolder: return "file folder, file_folder, filefolder" + case .openFileFolder: return "open file folder, open_file_folder, openfilefolder" + case .cardIndexDividers: return "card index dividers, card_index_dividers, cardindexdividers" case .date: return "calendar, date" case .calendar: return "calendar, tear-off calendar" - case .spiralNotePad: return "spiralnotepad, spiral notepad, spiral_note_pad" - case .spiralCalendarPad: return "spiralcalendarpad, spiral calendar, spiral_calendar_pad" - case .cardIndex: return "card index, cardindex, card_index" - case .chartWithUpwardsTrend: return "chartwithupwardstrend, chart with upwards trend, chart_with_upwards_trend" - case .chartWithDownwardsTrend: return "chartwithdownwardstrend, chart with downwards trend, chart_with_downwards_trend" - case .barChart: return "barchart, bar chart, bar_chart" + case .spiralNotePad: return "spiral notepad, spiral_note_pad, spiralnotepad" + case .spiralCalendarPad: return "spiral calendar, spiral_calendar_pad, spiralcalendarpad" + case .cardIndex: return "card index, card_index, cardindex" + case .chartWithUpwardsTrend: return "chart with upwards trend, chart_with_upwards_trend, chartwithupwardstrend" + case .chartWithDownwardsTrend: return "chart with downwards trend, chart_with_downwards_trend, chartwithdownwardstrend" + case .barChart: return "bar chart, bar_chart, barchart" case .clipboard: return "clipboard" case .pushpin: return "pushpin" case .roundPushpin: return "round pushpin, round_pushpin, roundpushpin" case .paperclip: return "paperclip" case .linkedPaperclips: return "linked paperclips, linked_paperclips, linkedpaperclips" - case .straightRuler: return "straightruler, straight ruler, straight_ruler" - case .triangularRuler: return "triangular ruler, triangularruler, triangular_ruler" + case .straightRuler: return "straight ruler, straight_ruler, straightruler" + case .triangularRuler: return "triangular ruler, triangular_ruler, triangularruler" case .scissors: return "black scissors, scissors" case .cardFileBox: return "card file box, card_file_box, cardfilebox" - case .fileCabinet: return "file_cabinet, filecabinet, file cabinet" + case .fileCabinet: return "file cabinet, file_cabinet, filecabinet" case .wastebasket: return "wastebasket" case .lock: return "lock" case .unlock: return "open lock, unlock" - case .lockWithInkPen: return "lock_with_ink_pen, lock with ink pen, lockwithinkpen" - case .closedLockWithKey: return "closedlockwithkey, closed_lock_with_key, closed lock with key" + case .lockWithInkPen: return "lock with ink pen, lock_with_ink_pen, lockwithinkpen" + case .closedLockWithKey: return "closed lock with key, closed_lock_with_key, closedlockwithkey" case .key: return "key" - case .oldKey: return "oldkey, old key, old_key" + case .oldKey: return "old key, old_key, oldkey" case .hammer: return "hammer" case .axe: return "axe" case .pick: return "pick" - case .hammerAndPick: return "hammerandpick, hammer and pick, hammer_and_pick" - case .hammerAndWrench: return "hammerandwrench, hammer_and_wrench, hammer and wrench" - case .daggerKnife: return "daggerknife, dagger_knife, dagger" - case .crossedSwords: return "crossedswords, crossed_swords, crossed swords" - case .gun: return "gun, pistol" + case .hammerAndPick: return "hammer and pick, hammer_and_pick, hammerandpick" + case .hammerAndWrench: return "hammer and wrench, hammer_and_wrench, hammerandwrench" + case .daggerKnife: return "dagger, dagger_knife, daggerknife" + case .crossedSwords: return "crossed swords, crossed_swords, crossedswords" + case .bomb: return "bomb" case .boomerang: return "boomerang" - case .bowAndArrow: return "bow_and_arrow, bowandarrow, bow and arrow" + case .bowAndArrow: return "bow and arrow, bow_and_arrow, bowandarrow" case .shield: return "shield" - case .carpentrySaw: return "carpentry_saw, carpentry saw, carpentrysaw" + case .carpentrySaw: return "carpentry saw, carpentry_saw, carpentrysaw" case .wrench: return "wrench" case .screwdriver: return "screwdriver" - case .nutAndBolt: return "nut_and_bolt, nut and bolt, nutandbolt" + case .nutAndBolt: return "nut and bolt, nut_and_bolt, nutandbolt" case .gear: return "gear" case .compression: return "clamp, compression" case .scales: return "balance scale, scales" @@ -1323,14 +1340,14 @@ extension Emoji { case .magnet: return "magnet" case .ladder: return "ladder" case .alembic: return "alembic" - case .testTube: return "test tube, testtube, test_tube" + case .testTube: return "test tube, test_tube, testtube" case .petriDish: return "petri dish, petri_dish, petridish" case .dna: return "dna, dna double helix" case .microscope: return "microscope" case .telescope: return "telescope" - case .satelliteAntenna: return "satelliteantenna, satellite_antenna, satellite antenna" + case .satelliteAntenna: return "satellite antenna, satellite_antenna, satelliteantenna" case .syringe: return "syringe" - case .dropOfBlood: return "drop of blood, dropofblood, drop_of_blood" + case .dropOfBlood: return "drop of blood, drop_of_blood, dropofblood" case .pill: return "pill" case .adhesiveBandage: return "adhesive bandage, adhesive_bandage, adhesivebandage" case .crutch: return "crutch" @@ -1341,92 +1358,95 @@ extension Emoji { case .mirror: return "mirror" case .window: return "window" case .bed: return "bed" - case .couchAndLamp: return "couch_and_lamp, couchandlamp, couch and lamp" + case .couchAndLamp: return "couch and lamp, couch_and_lamp, couchandlamp" case .chair: return "chair" case .toilet: return "toilet" case .plunger: return "plunger" case .shower: return "shower" case .bathtub: return "bathtub" - case .mouseTrap: return "mousetrap, mouse_trap, mouse trap" + case .mouseTrap: return "mouse trap, mouse_trap, mousetrap" case .razor: return "razor" - case .lotionBottle: return "lotionbottle, lotion_bottle, lotion bottle" - case .safetyPin: return "safety pin, safetypin, safety_pin" + case .lotionBottle: return "lotion bottle, lotion_bottle, lotionbottle" + case .safetyPin: return "safety pin, safety_pin, safetypin" case .broom: return "broom" case .basket: return "basket" - case .rollOfPaper: return "roll_of_paper, rollofpaper, roll of paper" + case .rollOfPaper: return "roll of paper, roll_of_paper, rollofpaper" case .bucket: return "bucket" - case .soap: return "soap, bar of soap" + case .soap: return "bar of soap, soap" case .bubbles: return "bubbles" case .toothbrush: return "toothbrush" case .sponge: return "sponge" case .fireExtinguisher: return "fire extinguisher, fire_extinguisher, fireextinguisher" - case .shoppingTrolley: return "shopping_trolley, shopping trolley, shoppingtrolley" + case .shoppingTrolley: return "shopping trolley, shopping_trolley, shoppingtrolley" case .smoking: return "smoking, smoking symbol" case .coffin: return "coffin" case .headstone: return "headstone" - case .funeralUrn: return "funeralurn, funeral urn, funeral_urn" + case .funeralUrn: return "funeral urn, funeral_urn, funeralurn" + case .nazarAmulet: return "nazar amulet, nazar_amulet, nazaramulet" + case .hamsa: return "hamsa" case .moyai: return "moyai" case .placard: return "placard" - case .identificationCard: return "identification_card, identification card, identificationcard" + case .identificationCard: return "identification card, identification_card, identificationcard" case .atm: return "atm, automated teller machine" - case .putLitterInItsPlace: return "putlitterinitsplace, put_litter_in_its_place, put litter in its place symbol" - case .potableWater: return "potablewater, potable water symbol, potable_water" - case .wheelchair: return "wheelchair symbol, wheelchair" + case .putLitterInItsPlace: return "put litter in its place symbol, put_litter_in_its_place, putlitterinitsplace" + case .potableWater: return "potable water symbol, potable_water, potablewater" + case .wheelchair: return "wheelchair, wheelchair symbol" case .mens: return "mens, mens symbol" - case .womens: return "womens symbol, womens" + case .womens: return "womens, womens symbol" case .restroom: return "restroom" - case .babySymbol: return "baby_symbol, babysymbol, baby symbol" - case .wc: return "wc, water closet" - case .passportControl: return "passport_control, passport control, passportcontrol" + case .babySymbol: return "baby symbol, baby_symbol, babysymbol" + case .wc: return "water closet, wc" + case .passportControl: return "passport control, passport_control, passportcontrol" case .customs: return "customs" - case .baggageClaim: return "baggageclaim, baggage claim, baggage_claim" - case .leftLuggage: return "left luggage, leftluggage, left_luggage" + case .baggageClaim: return "baggage claim, baggage_claim, baggageclaim" + case .leftLuggage: return "left luggage, left_luggage, leftluggage" case .warning: return "warning, warning sign" - case .childrenCrossing: return "children crossing, childrencrossing, children_crossing" + case .childrenCrossing: return "children crossing, children_crossing, childrencrossing" case .noEntry: return "no entry, no_entry, noentry" - case .noEntrySign: return "no entry sign, noentrysign, no_entry_sign" + case .noEntrySign: return "no entry sign, no_entry_sign, noentrysign" case .noBicycles: return "no bicycles, no_bicycles, nobicycles" - case .noSmoking: return "nosmoking, no_smoking, no smoking symbol" - case .doNotLitter: return "donotlitter, do not litter symbol, do_not_litter" - case .nonPotableWater: return "nonpotablewater, non-potable_water, non-potable water symbol" - case .noPedestrians: return "nopedestrians, no pedestrians, no_pedestrians" - case .noMobilePhones: return "no mobile phones, nomobilephones, no_mobile_phones" - case .underage: return "underage, no one under eighteen symbol" - case .radioactiveSign: return "radioactive, radioactivesign, radioactive_sign" - case .biohazardSign: return "biohazard, biohazardsign, biohazard_sign" - case .arrowUp: return "arrowup, arrow_up, upwards black arrow" - case .arrowUpperRight: return "north east arrow, arrow_upper_right, arrowupperright" - case .arrowRight: return "arrowright, black rightwards arrow, arrow_right" - case .arrowLowerRight: return "arrowlowerright, arrow_lower_right, south east arrow" - case .arrowDown: return "downwards black arrow, arrowdown, arrow_down" - case .arrowLowerLeft: return "arrow_lower_left, south west arrow, arrowlowerleft" - case .arrowLeft: return "arrow_left, leftwards black arrow, arrowleft" - case .arrowUpperLeft: return "arrow_upper_left, north west arrow, arrowupperleft" - case .arrowUpDown: return "arrowupdown, arrow_up_down, up down arrow" - case .leftRightArrow: return "left_right_arrow, left right arrow, leftrightarrow" - case .leftwardsArrowWithHook: return "leftwards_arrow_with_hook, leftwardsarrowwithhook, leftwards arrow with hook" - case .arrowRightHook: return "arrow_right_hook, rightwards arrow with hook, arrowrighthook" - case .arrowHeadingUp: return "arrow_heading_up, arrow pointing rightwards then curving upwards, arrowheadingup" - case .arrowHeadingDown: return "arrow_heading_down, arrow pointing rightwards then curving downwards, arrowheadingdown" - case .arrowsClockwise: return "clockwise downwards and upwards open circle arrows, arrowsclockwise, arrows_clockwise" - case .arrowsCounterclockwise: return "arrowscounterclockwise, arrows_counterclockwise, anticlockwise downwards and upwards open circle arrows" + case .noSmoking: return "no smoking symbol, no_smoking, nosmoking" + case .doNotLitter: return "do not litter symbol, do_not_litter, donotlitter" + case .nonPotableWater: return "non-potable water symbol, non-potable_water, nonpotablewater" + case .noPedestrians: return "no pedestrians, no_pedestrians, nopedestrians" + case .noMobilePhones: return "no mobile phones, no_mobile_phones, nomobilephones" + case .underage: return "no one under eighteen symbol, underage" + case .radioactiveSign: return "radioactive, radioactive_sign, radioactivesign" + case .biohazardSign: return "biohazard, biohazard_sign, biohazardsign" + case .arrowUp: return "arrow_up, arrowup, upwards black arrow" + case .arrowUpperRight: return "arrow_upper_right, arrowupperright, north east arrow" + case .arrowRight: return "arrow_right, arrowright, black rightwards arrow" + case .arrowLowerRight: return "arrow_lower_right, arrowlowerright, south east arrow" + case .arrowDown: return "arrow_down, arrowdown, downwards black arrow" + case .arrowLowerLeft: return "arrow_lower_left, arrowlowerleft, south west arrow" + case .arrowLeft: return "arrow_left, arrowleft, leftwards black arrow" + case .arrowUpperLeft: return "arrow_upper_left, arrowupperleft, north west arrow" + case .arrowUpDown: return "arrow_up_down, arrowupdown, up down arrow" + case .leftRightArrow: return "left right arrow, left_right_arrow, leftrightarrow" + case .leftwardsArrowWithHook: return "leftwards arrow with hook, leftwards_arrow_with_hook, leftwardsarrowwithhook" + case .arrowRightHook: return "arrow_right_hook, arrowrighthook, rightwards arrow with hook" + case .arrowHeadingUp: return "arrow pointing rightwards then curving upwards, arrow_heading_up, arrowheadingup" + case .arrowHeadingDown: return "arrow pointing rightwards then curving downwards, arrow_heading_down, arrowheadingdown" + case .arrowsClockwise: return "arrows_clockwise, arrowsclockwise, clockwise downwards and upwards open circle arrows" + case .arrowsCounterclockwise: return "anticlockwise downwards and upwards open circle arrows, arrows_counterclockwise, arrowscounterclockwise" case .back: return "back, back with leftwards arrow above" - case .end: return "end with leftwards arrow above, end" + case .end: return "end, end with leftwards arrow above" case .on: return "on, on with exclamation mark with left right arrow above" - case .soon: return "soon with rightwards arrow above, soon" + case .soon: return "soon, soon with rightwards arrow above" case .top: return "top, top with upwards arrow above" - case .placeOfWorship: return "place_of_worship, placeofworship, place of worship" - case .atomSymbol: return "atomsymbol, atom_symbol, atom symbol" - case .omSymbol: return "omsymbol, om, om_symbol" - case .starOfDavid: return "star_of_david, star of david, starofdavid" - case .wheelOfDharma: return "wheel of dharma, wheelofdharma, wheel_of_dharma" - case .yinYang: return "yin yang, yinyang, yin_yang" - case .latinCross: return "latin_cross, latin cross, latincross" - case .orthodoxCross: return "orthodox cross, orthodoxcross, orthodox_cross" - case .starAndCrescent: return "starandcrescent, star_and_crescent, star and crescent" - case .peaceSymbol: return "peacesymbol, peace_symbol, peace symbol" - case .menorahWithNineBranches: return "menorah_with_nine_branches, menorahwithninebranches, menorah with nine branches" - case .sixPointedStar: return "six_pointed_star, six pointed star with middle dot, sixpointedstar" + case .placeOfWorship: return "place of worship, place_of_worship, placeofworship" + case .atomSymbol: return "atom symbol, atom_symbol, atomsymbol" + case .omSymbol: return "om, om_symbol, omsymbol" + case .starOfDavid: return "star of david, star_of_david, starofdavid" + case .wheelOfDharma: return "wheel of dharma, wheel_of_dharma, wheelofdharma" + case .yinYang: return "yin yang, yin_yang, yinyang" + case .latinCross: return "latin cross, latin_cross, latincross" + case .orthodoxCross: return "orthodox cross, orthodox_cross, orthodoxcross" + case .starAndCrescent: return "star and crescent, star_and_crescent, starandcrescent" + case .peaceSymbol: return "peace symbol, peace_symbol, peacesymbol" + case .menorahWithNineBranches: return "menorah with nine branches, menorah_with_nine_branches, menorahwithninebranches" + case .sixPointedStar: return "six pointed star with middle dot, six_pointed_star, sixpointedstar" + case .khanda: return "khanda" case .aries: return "aries" case .taurus: return "taurus" case .gemini: return "gemini" @@ -1440,424 +1460,425 @@ extension Emoji { case .aquarius: return "aquarius" case .pisces: return "pisces" case .ophiuchus: return "ophiuchus" - case .twistedRightwardsArrows: return "twisted rightwards arrows, twistedrightwardsarrows, twisted_rightwards_arrows" - case .`repeat`: return "repeat, clockwise rightwards and leftwards open circle arrows, `repeat`" - case .repeatOne: return "repeatone, repeat_one, clockwise rightwards and leftwards open circle arrows with circled one overlay" - case .arrowForward: return "arrowforward, arrow_forward, black right-pointing triangle" - case .fastForward: return "fast_forward, fastforward, black right-pointing double triangle" - case .blackRightPointingDoubleTriangleWithVerticalBar: return "next track button, black_right_pointing_double_triangle_with_vertical_bar, blackrightpointingdoubletrianglewithverticalbar" + case .twistedRightwardsArrows: return "twisted rightwards arrows, twisted_rightwards_arrows, twistedrightwardsarrows" + case .`repeat`: return "`repeat`, clockwise rightwards and leftwards open circle arrows, repeat" + case .repeatOne: return "clockwise rightwards and leftwards open circle arrows with circled one overlay, repeat_one, repeatone" + case .arrowForward: return "arrow_forward, arrowforward, black right-pointing triangle" + case .fastForward: return "black right-pointing double triangle, fast_forward, fastforward" + case .blackRightPointingDoubleTriangleWithVerticalBar: return "black_right_pointing_double_triangle_with_vertical_bar, blackrightpointingdoubletrianglewithverticalbar, next track button" case .blackRightPointingTriangleWithDoubleVerticalBar: return "black_right_pointing_triangle_with_double_vertical_bar, blackrightpointingtrianglewithdoubleverticalbar, play or pause button" - case .arrowBackward: return "arrow_backward, black left-pointing triangle, arrowbackward" + case .arrowBackward: return "arrow_backward, arrowbackward, black left-pointing triangle" case .rewind: return "black left-pointing double triangle, rewind" - case .blackLeftPointingDoubleTriangleWithVerticalBar: return "last track button, blackleftpointingdoubletrianglewithverticalbar, black_left_pointing_double_triangle_with_vertical_bar" - case .arrowUpSmall: return "up-pointing small red triangle, arrowupsmall, arrow_up_small" - case .arrowDoubleUp: return "arrow_double_up, black up-pointing double triangle, arrowdoubleup" - case .arrowDownSmall: return "arrow_down_small, down-pointing small red triangle, arrowdownsmall" - case .arrowDoubleDown: return "arrowdoubledown, arrow_double_down, black down-pointing double triangle" - case .doubleVerticalBar: return "doubleverticalbar, double_vertical_bar, pause button" - case .blackSquareForStop: return "blacksquareforstop, black_square_for_stop, stop button" - case .blackCircleForRecord: return "record button, black_circle_for_record, blackcircleforrecord" - case .eject: return "eject button, eject" + case .blackLeftPointingDoubleTriangleWithVerticalBar: return "black_left_pointing_double_triangle_with_vertical_bar, blackleftpointingdoubletrianglewithverticalbar, last track button" + case .arrowUpSmall: return "arrow_up_small, arrowupsmall, up-pointing small red triangle" + case .arrowDoubleUp: return "arrow_double_up, arrowdoubleup, black up-pointing double triangle" + case .arrowDownSmall: return "arrow_down_small, arrowdownsmall, down-pointing small red triangle" + case .arrowDoubleDown: return "arrow_double_down, arrowdoubledown, black down-pointing double triangle" + case .doubleVerticalBar: return "double_vertical_bar, doubleverticalbar, pause button" + case .blackSquareForStop: return "black_square_for_stop, blacksquareforstop, stop button" + case .blackCircleForRecord: return "black_circle_for_record, blackcircleforrecord, record button" + case .eject: return "eject, eject button" case .cinema: return "cinema" - case .lowBrightness: return "lowbrightness, low brightness symbol, low_brightness" - case .highBrightness: return "high brightness symbol, highbrightness, high_brightness" - case .signalStrength: return "signal_strength, signalstrength, antenna with bars" - case .vibrationMode: return "vibration_mode, vibration mode, vibrationmode" - case .mobilePhoneOff: return "mobilephoneoff, mobile phone off, mobile_phone_off" - case .femaleSign: return "femalesign, female sign, female_sign" + case .lowBrightness: return "low brightness symbol, low_brightness, lowbrightness" + case .highBrightness: return "high brightness symbol, high_brightness, highbrightness" + case .signalStrength: return "antenna with bars, signal_strength, signalstrength" + case .wireless: return "wireless" + case .vibrationMode: return "vibration mode, vibration_mode, vibrationmode" + case .mobilePhoneOff: return "mobile phone off, mobile_phone_off, mobilephoneoff" + case .femaleSign: return "female sign, female_sign, femalesign" case .maleSign: return "male sign, male_sign, malesign" case .transgenderSymbol: return "transgender symbol, transgender_symbol, transgendersymbol" - case .heavyMultiplicationX: return "heavymultiplicationx, heavy_multiplication_x, heavy multiplication x" + case .heavyMultiplicationX: return "heavy multiplication x, heavy_multiplication_x, heavymultiplicationx" case .heavyPlusSign: return "heavy plus sign, heavy_plus_sign, heavyplussign" - case .heavyMinusSign: return "heavy_minus_sign, heavy minus sign, heavyminussign" - case .heavyDivisionSign: return "heavy division sign, heavydivisionsign, heavy_division_sign" - case .heavyEqualsSign: return "heavy equals sign, heavyequalssign, heavy_equals_sign" + case .heavyMinusSign: return "heavy minus sign, heavy_minus_sign, heavyminussign" + case .heavyDivisionSign: return "heavy division sign, heavy_division_sign, heavydivisionsign" + case .heavyEqualsSign: return "heavy equals sign, heavy_equals_sign, heavyequalssign" case .infinity: return "infinity" case .bangbang: return "bangbang, double exclamation mark" case .interrobang: return "exclamation question mark, interrobang" - case .question: return "question, black question mark ornament" - case .greyQuestion: return "greyquestion, grey_question, white question mark ornament" - case .greyExclamation: return "white exclamation mark ornament, greyexclamation, grey_exclamation" - case .exclamation: return "heavy exclamation mark symbol, exclamation, heavy_exclamation_mark" - case .wavyDash: return "wavy_dash, wavy dash, wavydash" - case .currencyExchange: return "currency exchange, currencyexchange, currency_exchange" - case .heavyDollarSign: return "heavydollarsign, heavy_dollar_sign, heavy dollar sign" + case .question: return "black question mark ornament, question" + case .greyQuestion: return "grey_question, greyquestion, white question mark ornament" + case .greyExclamation: return "grey_exclamation, greyexclamation, white exclamation mark ornament" + case .exclamation: return "exclamation, heavy exclamation mark symbol, heavy_exclamation_mark" + case .wavyDash: return "wavy dash, wavy_dash, wavydash" + case .currencyExchange: return "currency exchange, currency_exchange, currencyexchange" + case .heavyDollarSign: return "heavy dollar sign, heavy_dollar_sign, heavydollarsign" case .medicalSymbol: return "medical symbol, medical_symbol, medicalsymbol, staff_of_aesculapius" - case .recycle: return "recycle, black universal recycling symbol" - case .fleurDeLis: return "fleurdelis, fleur-de-lis, fleur_de_lis" + case .recycle: return "black universal recycling symbol, recycle" + case .fleurDeLis: return "fleur-de-lis, fleur_de_lis, fleurdelis" case .trident: return "trident, trident emblem" - case .nameBadge: return "namebadge, name_badge, name badge" - case .beginner: return "japanese symbol for beginner, beginner" - case .o: return "o, heavy large circle" + case .nameBadge: return "name badge, name_badge, namebadge" + case .beginner: return "beginner, japanese symbol for beginner" + case .o: return "heavy large circle, o" case .whiteCheckMark: return "white heavy check mark, white_check_mark, whitecheckmark" - case .ballotBoxWithCheck: return "ballotboxwithcheck, ballot_box_with_check, ballot box with check" - case .heavyCheckMark: return "heavy check mark, heavycheckmark, heavy_check_mark" - case .x: return "x, cross mark" - case .negativeSquaredCrossMark: return "negative_squared_cross_mark, negative squared cross mark, negativesquaredcrossmark" - case .curlyLoop: return "curly_loop, curlyloop, curly loop" + case .ballotBoxWithCheck: return "ballot box with check, ballot_box_with_check, ballotboxwithcheck" + case .heavyCheckMark: return "heavy check mark, heavy_check_mark, heavycheckmark" + case .x: return "cross mark, x" + case .negativeSquaredCrossMark: return "negative squared cross mark, negative_squared_cross_mark, negativesquaredcrossmark" + case .curlyLoop: return "curly loop, curly_loop, curlyloop" case .loop: return "double curly loop, loop" case .partAlternationMark: return "part alternation mark, part_alternation_mark, partalternationmark" - case .eightSpokedAsterisk: return "eight_spoked_asterisk, eight spoked asterisk, eightspokedasterisk" + case .eightSpokedAsterisk: return "eight spoked asterisk, eight_spoked_asterisk, eightspokedasterisk" case .eightPointedBlackStar: return "eight pointed black star, eight_pointed_black_star, eightpointedblackstar" case .sparkle: return "sparkle" case .copyright: return "copyright, copyright sign" case .registered: return "registered, registered sign" - case .tm: return "trade mark sign, tm" - case .hash: return "hash key, hash" - case .keycapStar: return "keycapstar, keycap_star, keycap: *" + case .tm: return "tm, trade mark sign" + case .hash: return "hash, hash key" + case .keycapStar: return "keycap: *, keycap_star, keycapstar" case .zero: return "keycap 0, zero" case .one: return "keycap 1, one" - case .two: return "two, keycap 2" - case .three: return "three, keycap 3" - case .four: return "keycap 4, four" + case .two: return "keycap 2, two" + case .three: return "keycap 3, three" + case .four: return "four, keycap 4" case .five: return "five, keycap 5" - case .six: return "six, keycap 6" - case .seven: return "seven, keycap 7" + case .six: return "keycap 6, six" + case .seven: return "keycap 7, seven" case .eight: return "eight, keycap 8" case .nine: return "keycap 9, nine" - case .keycapTen: return "keycap_ten, keycap ten, keycapten" - case .capitalAbcd: return "input symbol for latin capital letters, capitalabcd, capital_abcd" + case .keycapTen: return "keycap ten, keycap_ten, keycapten" + case .capitalAbcd: return "capital_abcd, capitalabcd, input symbol for latin capital letters" case .abcd: return "abcd, input symbol for latin small letters" case .oneTwoThreeFour: return "1234, input symbol for numbers, onetwothreefour" case .symbols: return "input symbol for symbols, symbols" case .abc: return "abc, input symbol for latin letters" case .a: return "a, negative squared latin capital letter a" - case .ab: return "negative squared ab, ab" + case .ab: return "ab, negative squared ab" case .b: return "b, negative squared latin capital letter b" case .cl: return "cl, squared cl" - case .cool: return "squared cool, cool" + case .cool: return "cool, squared cool" case .free: return "free, squared free" - case .informationSource: return "informationsource, information_source, information source" + case .informationSource: return "information source, information_source, informationsource" case .id: return "id, squared id" case .m: return "circled latin capital letter m, m" - case .new: return "squared new, new" + case .new: return "new, squared new" case .ng: return "ng, squared ng" - case .o2: return "o2, negative squared latin capital letter o" + case .o2: return "negative squared latin capital letter o, o2" case .ok: return "ok, squared ok" - case .parking: return "parking, negative squared latin capital letter p" + case .parking: return "negative squared latin capital letter p, parking" case .sos: return "sos, squared sos" case .up: return "squared up with exclamation mark, up" case .vs: return "squared vs, vs" - case .koko: return "squared katakana koko, koko" - case .sa: return "squared katakana sa, sa" + case .koko: return "koko, squared katakana koko" + case .sa: return "sa, squared katakana sa" case .u6708: return "squared cjk unified ideograph-6708, u6708" case .u6709: return "squared cjk unified ideograph-6709, u6709" case .u6307: return "squared cjk unified ideograph-6307, u6307" case .ideographAdvantage: return "circled ideograph advantage, ideograph_advantage, ideographadvantage" - case .u5272: return "u5272, squared cjk unified ideograph-5272" - case .u7121: return "u7121, squared cjk unified ideograph-7121" - case .u7981: return "u7981, squared cjk unified ideograph-7981" - case .accept: return "circled ideograph accept, accept" + case .u5272: return "squared cjk unified ideograph-5272, u5272" + case .u7121: return "squared cjk unified ideograph-7121, u7121" + case .u7981: return "squared cjk unified ideograph-7981, u7981" + case .accept: return "accept, circled ideograph accept" case .u7533: return "squared cjk unified ideograph-7533, u7533" case .u5408: return "squared cjk unified ideograph-5408, u5408" case .u7a7a: return "squared cjk unified ideograph-7a7a, u7a7a" - case .congratulations: return "congratulations, circled ideograph congratulation" - case .secret: return "secret, circled ideograph secret" + case .congratulations: return "circled ideograph congratulation, congratulations" + case .secret: return "circled ideograph secret, secret" case .u55b6: return "squared cjk unified ideograph-55b6, u55b6" case .u6e80: return "squared cjk unified ideograph-6e80, u6e80" - case .redCircle: return "red_circle, large red circle, redcircle" - case .largeOrangeCircle: return "large_orange_circle, large orange circle, largeorangecircle" - case .largeYellowCircle: return "largeyellowcircle, large_yellow_circle, large yellow circle" - case .largeGreenCircle: return "largegreencircle, large green circle, large_green_circle" - case .largeBlueCircle: return "large_blue_circle, large blue circle, largebluecircle" - case .largePurpleCircle: return "large_purple_circle, large purple circle, largepurplecircle" - case .largeBrownCircle: return "largebrowncircle, large brown circle, large_brown_circle" + case .redCircle: return "large red circle, red_circle, redcircle" + case .largeOrangeCircle: return "large orange circle, large_orange_circle, largeorangecircle" + case .largeYellowCircle: return "large yellow circle, large_yellow_circle, largeyellowcircle" + case .largeGreenCircle: return "large green circle, large_green_circle, largegreencircle" + case .largeBlueCircle: return "large blue circle, large_blue_circle, largebluecircle" + case .largePurpleCircle: return "large purple circle, large_purple_circle, largepurplecircle" + case .largeBrownCircle: return "large brown circle, large_brown_circle, largebrowncircle" case .blackCircle: return "black_circle, blackcircle, medium black circle" - case .whiteCircle: return "white_circle, medium white circle, whitecircle" - case .largeRedSquare: return "large red square, largeredsquare, large_red_square" - case .largeOrangeSquare: return "large orange square, largeorangesquare, large_orange_square" + case .whiteCircle: return "medium white circle, white_circle, whitecircle" + case .largeRedSquare: return "large red square, large_red_square, largeredsquare" + case .largeOrangeSquare: return "large orange square, large_orange_square, largeorangesquare" case .largeYellowSquare: return "large yellow square, large_yellow_square, largeyellowsquare" - case .largeGreenSquare: return "large_green_square, large green square, largegreensquare" - case .largeBlueSquare: return "large blue square, largebluesquare, large_blue_square" + case .largeGreenSquare: return "large green square, large_green_square, largegreensquare" + case .largeBlueSquare: return "large blue square, large_blue_square, largebluesquare" case .largePurpleSquare: return "large purple square, large_purple_square, largepurplesquare" - case .largeBrownSquare: return "largebrownsquare, large_brown_square, large brown square" - case .blackLargeSquare: return "black_large_square, blacklargesquare, black large square" - case .whiteLargeSquare: return "whitelargesquare, white_large_square, white large square" - case .blackMediumSquare: return "black medium square, blackmediumsquare, black_medium_square" - case .whiteMediumSquare: return "white_medium_square, white medium square, whitemediumsquare" - case .blackMediumSmallSquare: return "black medium small square, blackmediumsmallsquare, black_medium_small_square" - case .whiteMediumSmallSquare: return "white_medium_small_square, whitemediumsmallsquare, white medium small square" - case .blackSmallSquare: return "black_small_square, blacksmallsquare, black small square" - case .whiteSmallSquare: return "white_small_square, white small square, whitesmallsquare" - case .largeOrangeDiamond: return "largeorangediamond, large orange diamond, large_orange_diamond" - case .largeBlueDiamond: return "large_blue_diamond, large blue diamond, largebluediamond" - case .smallOrangeDiamond: return "smallorangediamond, small orange diamond, small_orange_diamond" - case .smallBlueDiamond: return "small blue diamond, smallbluediamond, small_blue_diamond" - case .smallRedTriangle: return "small_red_triangle, up-pointing red triangle, smallredtriangle" - case .smallRedTriangleDown: return "small_red_triangle_down, down-pointing red triangle, smallredtriangledown" + case .largeBrownSquare: return "large brown square, large_brown_square, largebrownsquare" + case .blackLargeSquare: return "black large square, black_large_square, blacklargesquare" + case .whiteLargeSquare: return "white large square, white_large_square, whitelargesquare" + case .blackMediumSquare: return "black medium square, black_medium_square, blackmediumsquare" + case .whiteMediumSquare: return "white medium square, white_medium_square, whitemediumsquare" + case .blackMediumSmallSquare: return "black medium small square, black_medium_small_square, blackmediumsmallsquare" + case .whiteMediumSmallSquare: return "white medium small square, white_medium_small_square, whitemediumsmallsquare" + case .blackSmallSquare: return "black small square, black_small_square, blacksmallsquare" + case .whiteSmallSquare: return "white small square, white_small_square, whitesmallsquare" + case .largeOrangeDiamond: return "large orange diamond, large_orange_diamond, largeorangediamond" + case .largeBlueDiamond: return "large blue diamond, large_blue_diamond, largebluediamond" + case .smallOrangeDiamond: return "small orange diamond, small_orange_diamond, smallorangediamond" + case .smallBlueDiamond: return "small blue diamond, small_blue_diamond, smallbluediamond" + case .smallRedTriangle: return "small_red_triangle, smallredtriangle, up-pointing red triangle" + case .smallRedTriangleDown: return "down-pointing red triangle, small_red_triangle_down, smallredtriangledown" case .diamondShapeWithADotInside: return "diamond shape with a dot inside, diamond_shape_with_a_dot_inside, diamondshapewithadotinside" - case .radioButton: return "radio button, radiobutton, radio_button" - case .whiteSquareButton: return "whitesquarebutton, white_square_button, white square button" - case .blackSquareButton: return "blacksquarebutton, black_square_button, black square button" - case .checkeredFlag: return "chequered flag, checkered_flag, checkeredflag" - case .triangularFlagOnPost: return "triangularflagonpost, triangular_flag_on_post, triangular flag on post" - case .crossedFlags: return "crossed flags, crossedflags, crossed_flags" + case .radioButton: return "radio button, radio_button, radiobutton" + case .whiteSquareButton: return "white square button, white_square_button, whitesquarebutton" + case .blackSquareButton: return "black square button, black_square_button, blacksquarebutton" + case .checkeredFlag: return "checkered_flag, checkeredflag, chequered flag" + case .triangularFlagOnPost: return "triangular flag on post, triangular_flag_on_post, triangularflagonpost" + case .crossedFlags: return "crossed flags, crossed_flags, crossedflags" case .wavingBlackFlag: return "waving black flag, waving_black_flag, wavingblackflag" - case .wavingWhiteFlag: return "wavingwhiteflag, waving_white_flag, white flag" - case .rainbowFlag: return "rainbowflag, rainbow-flag, rainbow flag" + case .wavingWhiteFlag: return "waving_white_flag, wavingwhiteflag, white flag" + case .rainbowFlag: return "rainbow flag, rainbow-flag, rainbowflag" case .transgenderFlag: return "transgender flag, transgender_flag, transgenderflag" - case .pirateFlag: return "pirate flag, pirateflag, pirate_flag" - case .flagAc: return "flag-ac, ascension island flag, flagac" - case .flagAd: return "flagad, flag-ad, andorra flag" - case .flagAe: return "united arab emirates flag, flagae, flag-ae" - case .flagAf: return "afghanistan flag, flagaf, flag-af" - case .flagAg: return "antigua & barbuda flag, flagag, flag-ag" - case .flagAi: return "flagai, flag-ai, anguilla flag" - case .flagAl: return "flagal, flag-al, albania flag" - case .flagAm: return "armenia flag, flagam, flag-am" - case .flagAo: return "flag-ao, angola flag, flagao" - case .flagAq: return "flag-aq, antarctica flag, flagaq" + case .pirateFlag: return "pirate flag, pirate_flag, pirateflag" + case .flagAc: return "ascension island flag, flag-ac, flagac" + case .flagAd: return "andorra flag, flag-ad, flagad" + case .flagAe: return "flag-ae, flagae, united arab emirates flag" + case .flagAf: return "afghanistan flag, flag-af, flagaf" + case .flagAg: return "antigua & barbuda flag, flag-ag, flagag" + case .flagAi: return "anguilla flag, flag-ai, flagai" + case .flagAl: return "albania flag, flag-al, flagal" + case .flagAm: return "armenia flag, flag-am, flagam" + case .flagAo: return "angola flag, flag-ao, flagao" + case .flagAq: return "antarctica flag, flag-aq, flagaq" case .flagAr: return "argentina flag, flag-ar, flagar" case .flagAs: return "american samoa flag, flag-as, flagas" - case .flagAt: return "flag-at, austria flag, flagat" - case .flagAu: return "flag-au, flagau, australia flag" + case .flagAt: return "austria flag, flag-at, flagat" + case .flagAu: return "australia flag, flag-au, flagau" case .flagAw: return "aruba flag, flag-aw, flagaw" case .flagAx: return "flag-ax, flagax, åland islands flag" - case .flagAz: return "flagaz, flag-az, azerbaijan flag" - case .flagBa: return "flagba, flag-ba, bosnia & herzegovina flag" - case .flagBb: return "barbados flag, flagbb, flag-bb" - case .flagBd: return "flag-bd, bangladesh flag, flagbd" - case .flagBe: return "belgium flag, flagbe, flag-be" - case .flagBf: return "burkina faso flag, flagbf, flag-bf" - case .flagBg: return "flagbg, bulgaria flag, flag-bg" - case .flagBh: return "bahrain flag, flagbh, flag-bh" - case .flagBi: return "flagbi, flag-bi, burundi flag" - case .flagBj: return "flagbj, flag-bj, benin flag" - case .flagBl: return "flag-bl, st. barthélemy flag, flagbl" + case .flagAz: return "azerbaijan flag, flag-az, flagaz" + case .flagBa: return "bosnia & herzegovina flag, flag-ba, flagba" + case .flagBb: return "barbados flag, flag-bb, flagbb" + case .flagBd: return "bangladesh flag, flag-bd, flagbd" + case .flagBe: return "belgium flag, flag-be, flagbe" + case .flagBf: return "burkina faso flag, flag-bf, flagbf" + case .flagBg: return "bulgaria flag, flag-bg, flagbg" + case .flagBh: return "bahrain flag, flag-bh, flagbh" + case .flagBi: return "burundi flag, flag-bi, flagbi" + case .flagBj: return "benin flag, flag-bj, flagbj" + case .flagBl: return "flag-bl, flagbl, st. barthélemy flag" case .flagBm: return "bermuda flag, flag-bm, flagbm" - case .flagBn: return "flag-bn, brunei flag, flagbn" - case .flagBo: return "flag-bo, flagbo, bolivia flag" - case .flagBq: return "flagbq, caribbean netherlands flag, flag-bq" - case .flagBr: return "flag-br, flagbr, brazil flag" - case .flagBs: return "flagbs, flag-bs, bahamas flag" - case .flagBt: return "flagbt, flag-bt, bhutan flag" + case .flagBn: return "brunei flag, flag-bn, flagbn" + case .flagBo: return "bolivia flag, flag-bo, flagbo" + case .flagBq: return "caribbean netherlands flag, flag-bq, flagbq" + case .flagBr: return "brazil flag, flag-br, flagbr" + case .flagBs: return "bahamas flag, flag-bs, flagbs" + case .flagBt: return "bhutan flag, flag-bt, flagbt" case .flagBv: return "bouvet island flag, flag-bv, flagbv" case .flagBw: return "botswana flag, flag-bw, flagbw" - case .flagBy: return "flag-by, belarus flag, flagby" + case .flagBy: return "belarus flag, flag-by, flagby" case .flagBz: return "belize flag, flag-bz, flagbz" - case .flagCa: return "flag-ca, flagca, canada flag" - case .flagCc: return "flag-cc, cocos (keeling) islands flag, flagcc" - case .flagCd: return "flag-cd, flagcd, congo - kinshasa flag" - case .flagCf: return "central african republic flag, flagcf, flag-cf" - case .flagCg: return "congo - brazzaville flag, flagcg, flag-cg" - case .flagCh: return "switzerland flag, flagch, flag-ch" - case .flagCi: return "flagci, côte d’ivoire flag, flag-ci" - case .flagCk: return "flagck, cook islands flag, flag-ck" - case .flagCl: return "flagcl, chile flag, flag-cl" - case .flagCm: return "flagcm, flag-cm, cameroon flag" + case .flagCa: return "canada flag, flag-ca, flagca" + case .flagCc: return "cocos (keeling) islands flag, flag-cc, flagcc" + case .flagCd: return "congo - kinshasa flag, flag-cd, flagcd" + case .flagCf: return "central african republic flag, flag-cf, flagcf" + case .flagCg: return "congo - brazzaville flag, flag-cg, flagcg" + case .flagCh: return "flag-ch, flagch, switzerland flag" + case .flagCi: return "côte d’ivoire flag, flag-ci, flagci" + case .flagCk: return "cook islands flag, flag-ck, flagck" + case .flagCl: return "chile flag, flag-cl, flagcl" + case .flagCm: return "cameroon flag, flag-cm, flagcm" case .cn: return "china flag, cn, flag-cn" - case .flagCo: return "flagco, flag-co, colombia flag" - case .flagCp: return "clipperton island flag, flagcp, flag-cp" - case .flagCr: return "flagcr, costa rica flag, flag-cr" - case .flagCu: return "flag-cu, cuba flag, flagcu" - case .flagCv: return "flag-cv, flagcv, cape verde flag" - case .flagCw: return "flagcw, flag-cw, curaçao flag" + case .flagCo: return "colombia flag, flag-co, flagco" + case .flagCp: return "clipperton island flag, flag-cp, flagcp" + case .flagCr: return "costa rica flag, flag-cr, flagcr" + case .flagCu: return "cuba flag, flag-cu, flagcu" + case .flagCv: return "cape verde flag, flag-cv, flagcv" + case .flagCw: return "curaçao flag, flag-cw, flagcw" case .flagCx: return "christmas island flag, flag-cx, flagcx" - case .flagCy: return "cyprus flag, flagcy, flag-cy" - case .flagCz: return "flag-cz, czechia flag, flagcz" - case .de: return "flag-de, de, germany flag" - case .flagDg: return "diego garcia flag, flagdg, flag-dg" - case .flagDj: return "flag-dj, djibouti flag, flagdj" - case .flagDk: return "flag-dk, denmark flag, flagdk" - case .flagDm: return "flag-dm, flagdm, dominica flag" - case .flagDo: return "dominican republic flag, flagdo, flag-do" - case .flagDz: return "flag-dz, algeria flag, flagdz" - case .flagEa: return "ceuta & melilla flag, flagea, flag-ea" - case .flagEc: return "flag-ec, ecuador flag, flagec" - case .flagEe: return "flag-ee, estonia flag, flagee" + case .flagCy: return "cyprus flag, flag-cy, flagcy" + case .flagCz: return "czechia flag, flag-cz, flagcz" + case .de: return "de, flag-de, germany flag" + case .flagDg: return "diego garcia flag, flag-dg, flagdg" + case .flagDj: return "djibouti flag, flag-dj, flagdj" + case .flagDk: return "denmark flag, flag-dk, flagdk" + case .flagDm: return "dominica flag, flag-dm, flagdm" + case .flagDo: return "dominican republic flag, flag-do, flagdo" + case .flagDz: return "algeria flag, flag-dz, flagdz" + case .flagEa: return "ceuta & melilla flag, flag-ea, flagea" + case .flagEc: return "ecuador flag, flag-ec, flagec" + case .flagEe: return "estonia flag, flag-ee, flagee" case .flagEg: return "egypt flag, flag-eg, flageg" - case .flagEh: return "flag-eh, western sahara flag, flageh" - case .flagEr: return "flag-er, eritrea flag, flager" - case .es: return "es, spain flag, flag-es" - case .flagEt: return "flag-et, ethiopia flag, flaget" - case .flagEu: return "flag-eu, european union flag, flageu" - case .flagFi: return "finland flag, flagfi, flag-fi" - case .flagFj: return "flagfj, flag-fj, fiji flag" - case .flagFk: return "flag-fk, flagfk, falkland islands flag" - case .flagFm: return "flagfm, flag-fm, micronesia flag" - case .flagFo: return "flag-fo, faroe islands flag, flagfo" - case .fr: return "flag-fr, france flag, fr" - case .flagGa: return "gabon flag, flag-ga, flagga" - case .gb: return "gb, uk, united kingdom flag, flag-gb" - case .flagGd: return "flaggd, flag-gd, grenada flag" - case .flagGe: return "georgia flag, flagge, flag-ge" - case .flagGf: return "flag-gf, french guiana flag, flaggf" - case .flagGg: return "guernsey flag, flaggg, flag-gg" - case .flagGh: return "flaggh, flag-gh, ghana flag" - case .flagGi: return "flag-gi, gibraltar flag, flaggi" + case .flagEh: return "flag-eh, flageh, western sahara flag" + case .flagEr: return "eritrea flag, flag-er, flager" + case .es: return "es, flag-es, spain flag" + case .flagEt: return "ethiopia flag, flag-et, flaget" + case .flagEu: return "european union flag, flag-eu, flageu" + case .flagFi: return "finland flag, flag-fi, flagfi" + case .flagFj: return "fiji flag, flag-fj, flagfj" + case .flagFk: return "falkland islands flag, flag-fk, flagfk" + case .flagFm: return "flag-fm, flagfm, micronesia flag" + case .flagFo: return "faroe islands flag, flag-fo, flagfo" + case .fr: return "flag-fr, fr, france flag" + case .flagGa: return "flag-ga, flagga, gabon flag" + case .gb: return "flag-gb, gb, uk, united kingdom flag" + case .flagGd: return "flag-gd, flaggd, grenada flag" + case .flagGe: return "flag-ge, flagge, georgia flag" + case .flagGf: return "flag-gf, flaggf, french guiana flag" + case .flagGg: return "flag-gg, flaggg, guernsey flag" + case .flagGh: return "flag-gh, flaggh, ghana flag" + case .flagGi: return "flag-gi, flaggi, gibraltar flag" case .flagGl: return "flag-gl, flaggl, greenland flag" - case .flagGm: return "flag-gm, gambia flag, flaggm" - case .flagGn: return "flaggn, guinea flag, flag-gn" - case .flagGp: return "guadeloupe flag, flag-gp, flaggp" - case .flagGq: return "flag-gq, equatorial guinea flag, flaggq" + case .flagGm: return "flag-gm, flaggm, gambia flag" + case .flagGn: return "flag-gn, flaggn, guinea flag" + case .flagGp: return "flag-gp, flaggp, guadeloupe flag" + case .flagGq: return "equatorial guinea flag, flag-gq, flaggq" case .flagGr: return "flag-gr, flaggr, greece flag" - case .flagGs: return "flag-gs, south georgia & south sandwich islands flag, flaggs" + case .flagGs: return "flag-gs, flaggs, south georgia & south sandwich islands flag" case .flagGt: return "flag-gt, flaggt, guatemala flag" - case .flagGu: return "flaggu, guam flag, flag-gu" - case .flagGw: return "guinea-bissau flag, flag-gw, flaggw" - case .flagGy: return "flaggy, flag-gy, guyana flag" - case .flagHk: return "flag-hk, hong kong sar china flag, flaghk" + case .flagGu: return "flag-gu, flaggu, guam flag" + case .flagGw: return "flag-gw, flaggw, guinea-bissau flag" + case .flagGy: return "flag-gy, flaggy, guyana flag" + case .flagHk: return "flag-hk, flaghk, hong kong sar china flag" case .flagHm: return "flag-hm, flaghm, heard & mcdonald islands flag" - case .flagHn: return "flag-hn, honduras flag, flaghn" - case .flagHr: return "flaghr, croatia flag, flag-hr" - case .flagHt: return "flag-ht, haiti flag, flaght" - case .flagHu: return "flaghu, flag-hu, hungary flag" - case .flagIc: return "flagic, flag-ic, canary islands flag" - case .flagId: return "flagid, flag-id, indonesia flag" - case .flagIe: return "ireland flag, flagie, flag-ie" - case .flagIl: return "flag-il, israel flag, flagil" - case .flagIm: return "isle of man flag, flag-im, flagim" - case .flagIn: return "india flag, flagin, flag-in" - case .flagIo: return "flag-io, british indian ocean territory flag, flagio" - case .flagIq: return "flagiq, iraq flag, flag-iq" - case .flagIr: return "flag-ir, iran flag, flagir" - case .flagIs: return "flag-is, iceland flag, flagis" - case .it: return "it, italy flag, flag-it" - case .flagJe: return "jersey flag, flagje, flag-je" - case .flagJm: return "flag-jm, jamaica flag, flagjm" - case .flagJo: return "flag-jo, jordan flag, flagjo" - case .jp: return "japan flag, jp, flag-jp" - case .flagKe: return "kenya flag, flag-ke, flagke" - case .flagKg: return "kyrgyzstan flag, flagkg, flag-kg" - case .flagKh: return "cambodia flag, flagkh, flag-kh" - case .flagKi: return "flagki, flag-ki, kiribati flag" - case .flagKm: return "flag-km, comoros flag, flagkm" - case .flagKn: return "st. kitts & nevis flag, flagkn, flag-kn" - case .flagKp: return "flag-kp, north korea flag, flagkp" - case .kr: return "kr, south korea flag, flag-kr" + case .flagHn: return "flag-hn, flaghn, honduras flag" + case .flagHr: return "croatia flag, flag-hr, flaghr" + case .flagHt: return "flag-ht, flaght, haiti flag" + case .flagHu: return "flag-hu, flaghu, hungary flag" + case .flagIc: return "canary islands flag, flag-ic, flagic" + case .flagId: return "flag-id, flagid, indonesia flag" + case .flagIe: return "flag-ie, flagie, ireland flag" + case .flagIl: return "flag-il, flagil, israel flag" + case .flagIm: return "flag-im, flagim, isle of man flag" + case .flagIn: return "flag-in, flagin, india flag" + case .flagIo: return "british indian ocean territory flag, flag-io, flagio" + case .flagIq: return "flag-iq, flagiq, iraq flag" + case .flagIr: return "flag-ir, flagir, iran flag" + case .flagIs: return "flag-is, flagis, iceland flag" + case .it: return "flag-it, it, italy flag" + case .flagJe: return "flag-je, flagje, jersey flag" + case .flagJm: return "flag-jm, flagjm, jamaica flag" + case .flagJo: return "flag-jo, flagjo, jordan flag" + case .jp: return "flag-jp, japan flag, jp" + case .flagKe: return "flag-ke, flagke, kenya flag" + case .flagKg: return "flag-kg, flagkg, kyrgyzstan flag" + case .flagKh: return "cambodia flag, flag-kh, flagkh" + case .flagKi: return "flag-ki, flagki, kiribati flag" + case .flagKm: return "comoros flag, flag-km, flagkm" + case .flagKn: return "flag-kn, flagkn, st. kitts & nevis flag" + case .flagKp: return "flag-kp, flagkp, north korea flag" + case .kr: return "flag-kr, kr, south korea flag" case .flagKw: return "flag-kw, flagkw, kuwait flag" - case .flagKy: return "flagky, cayman islands flag, flag-ky" - case .flagKz: return "flag-kz, kazakhstan flag, flagkz" - case .flagLa: return "flagla, flag-la, laos flag" - case .flagLb: return "flaglb, lebanon flag, flag-lb" - case .flagLc: return "st. lucia flag, flag-lc, flaglc" - case .flagLi: return "flag-li, liechtenstein flag, flagli" + case .flagKy: return "cayman islands flag, flag-ky, flagky" + case .flagKz: return "flag-kz, flagkz, kazakhstan flag" + case .flagLa: return "flag-la, flagla, laos flag" + case .flagLb: return "flag-lb, flaglb, lebanon flag" + case .flagLc: return "flag-lc, flaglc, st. lucia flag" + case .flagLi: return "flag-li, flagli, liechtenstein flag" case .flagLk: return "flag-lk, flaglk, sri lanka flag" - case .flagLr: return "flaglr, liberia flag, flag-lr" - case .flagLs: return "flagls, flag-ls, lesotho flag" - case .flagLt: return "lithuania flag, flaglt, flag-lt" - case .flagLu: return "luxembourg flag, flaglu, flag-lu" - case .flagLv: return "flaglv, latvia flag, flag-lv" - case .flagLy: return "libya flag, flagly, flag-ly" - case .flagMa: return "flag-ma, morocco flag, flagma" - case .flagMc: return "flag-mc, monaco flag, flagmc" - case .flagMd: return "flagmd, flag-md, moldova flag" - case .flagMe: return "montenegro flag, flag-me, flagme" - case .flagMf: return "flagmf, flag-mf, st. martin flag" - case .flagMg: return "flag-mg, madagascar flag, flagmg" + case .flagLr: return "flag-lr, flaglr, liberia flag" + case .flagLs: return "flag-ls, flagls, lesotho flag" + case .flagLt: return "flag-lt, flaglt, lithuania flag" + case .flagLu: return "flag-lu, flaglu, luxembourg flag" + case .flagLv: return "flag-lv, flaglv, latvia flag" + case .flagLy: return "flag-ly, flagly, libya flag" + case .flagMa: return "flag-ma, flagma, morocco flag" + case .flagMc: return "flag-mc, flagmc, monaco flag" + case .flagMd: return "flag-md, flagmd, moldova flag" + case .flagMe: return "flag-me, flagme, montenegro flag" + case .flagMf: return "flag-mf, flagmf, st. martin flag" + case .flagMg: return "flag-mg, flagmg, madagascar flag" case .flagMh: return "flag-mh, flagmh, marshall islands flag" - case .flagMk: return "flagmk, flag-mk, north macedonia flag" - case .flagMl: return "flag-ml, mali flag, flagml" + case .flagMk: return "flag-mk, flagmk, north macedonia flag" + case .flagMl: return "flag-ml, flagml, mali flag" case .flagMm: return "flag-mm, flagmm, myanmar (burma) flag" case .flagMn: return "flag-mn, flagmn, mongolia flag" - case .flagMo: return "flag-mo, macao sar china flag, flagmo" - case .flagMp: return "flagmp, northern mariana islands flag, flag-mp" - case .flagMq: return "martinique flag, flagmq, flag-mq" - case .flagMr: return "flagmr, mauritania flag, flag-mr" - case .flagMs: return "montserrat flag, flagms, flag-ms" - case .flagMt: return "flag-mt, malta flag, flagmt" + case .flagMo: return "flag-mo, flagmo, macao sar china flag" + case .flagMp: return "flag-mp, flagmp, northern mariana islands flag" + case .flagMq: return "flag-mq, flagmq, martinique flag" + case .flagMr: return "flag-mr, flagmr, mauritania flag" + case .flagMs: return "flag-ms, flagms, montserrat flag" + case .flagMt: return "flag-mt, flagmt, malta flag" case .flagMu: return "flag-mu, flagmu, mauritius flag" - case .flagMv: return "maldives flag, flag-mv, flagmv" - case .flagMw: return "flagmw, flag-mw, malawi flag" - case .flagMx: return "mexico flag, flagmx, flag-mx" - case .flagMy: return "flag-my, malaysia flag, flagmy" - case .flagMz: return "flagmz, flag-mz, mozambique flag" - case .flagNa: return "flagna, namibia flag, flag-na" - case .flagNc: return "new caledonia flag, flagnc, flag-nc" - case .flagNe: return "flagne, niger flag, flag-ne" - case .flagNf: return "flagnf, flag-nf, norfolk island flag" - case .flagNg: return "nigeria flag, flag-ng, flagng" - case .flagNi: return "flag-ni, nicaragua flag, flagni" - case .flagNl: return "flag-nl, netherlands flag, flagnl" - case .flagNo: return "norway flag, flagno, flag-no" - case .flagNp: return "flagnp, flag-np, nepal flag" - case .flagNr: return "flagnr, flag-nr, nauru flag" - case .flagNu: return "flag-nu, niue flag, flagnu" - case .flagNz: return "new zealand flag, flagnz, flag-nz" - case .flagOm: return "flagom, oman flag, flag-om" - case .flagPa: return "panama flag, flagpa, flag-pa" - case .flagPe: return "peru flag, flagpe, flag-pe" - case .flagPf: return "flagpf, flag-pf, french polynesia flag" - case .flagPg: return "flagpg, flag-pg, papua new guinea flag" + case .flagMv: return "flag-mv, flagmv, maldives flag" + case .flagMw: return "flag-mw, flagmw, malawi flag" + case .flagMx: return "flag-mx, flagmx, mexico flag" + case .flagMy: return "flag-my, flagmy, malaysia flag" + case .flagMz: return "flag-mz, flagmz, mozambique flag" + case .flagNa: return "flag-na, flagna, namibia flag" + case .flagNc: return "flag-nc, flagnc, new caledonia flag" + case .flagNe: return "flag-ne, flagne, niger flag" + case .flagNf: return "flag-nf, flagnf, norfolk island flag" + case .flagNg: return "flag-ng, flagng, nigeria flag" + case .flagNi: return "flag-ni, flagni, nicaragua flag" + case .flagNl: return "flag-nl, flagnl, netherlands flag" + case .flagNo: return "flag-no, flagno, norway flag" + case .flagNp: return "flag-np, flagnp, nepal flag" + case .flagNr: return "flag-nr, flagnr, nauru flag" + case .flagNu: return "flag-nu, flagnu, niue flag" + case .flagNz: return "flag-nz, flagnz, new zealand flag" + case .flagOm: return "flag-om, flagom, oman flag" + case .flagPa: return "flag-pa, flagpa, panama flag" + case .flagPe: return "flag-pe, flagpe, peru flag" + case .flagPf: return "flag-pf, flagpf, french polynesia flag" + case .flagPg: return "flag-pg, flagpg, papua new guinea flag" case .flagPh: return "flag-ph, flagph, philippines flag" - case .flagPk: return "flagpk, flag-pk, pakistan flag" + case .flagPk: return "flag-pk, flagpk, pakistan flag" case .flagPl: return "flag-pl, flagpl, poland flag" - case .flagPm: return "flag-pm, st. pierre & miquelon flag, flagpm" - case .flagPn: return "flagpn, pitcairn islands flag, flag-pn" - case .flagPr: return "puerto rico flag, flagpr, flag-pr" - case .flagPs: return "flag-ps, palestinian territories flag, flagps" - case .flagPt: return "flag-pt, portugal flag, flagpt" - case .flagPw: return "palau flag, flagpw, flag-pw" - case .flagPy: return "flagpy, flag-py, paraguay flag" - case .flagQa: return "flagqa, qatar flag, flag-qa" + case .flagPm: return "flag-pm, flagpm, st. pierre & miquelon flag" + case .flagPn: return "flag-pn, flagpn, pitcairn islands flag" + case .flagPr: return "flag-pr, flagpr, puerto rico flag" + case .flagPs: return "flag-ps, flagps, palestinian territories flag" + case .flagPt: return "flag-pt, flagpt, portugal flag" + case .flagPw: return "flag-pw, flagpw, palau flag" + case .flagPy: return "flag-py, flagpy, paraguay flag" + case .flagQa: return "flag-qa, flagqa, qatar flag" case .flagRe: return "flag-re, flagre, réunion flag" - case .flagRo: return "flag-ro, romania flag, flagro" - case .flagRs: return "flagrs, flag-rs, serbia flag" - case .ru: return "russia flag, ru, flag-ru" - case .flagRw: return "rwanda flag, flag-rw, flagrw" + case .flagRo: return "flag-ro, flagro, romania flag" + case .flagRs: return "flag-rs, flagrs, serbia flag" + case .ru: return "flag-ru, ru, russia flag" + case .flagRw: return "flag-rw, flagrw, rwanda flag" case .flagSa: return "flag-sa, flagsa, saudi arabia flag" - case .flagSb: return "solomon islands flag, flag-sb, flagsb" - case .flagSc: return "flagsc, seychelles flag, flag-sc" + case .flagSb: return "flag-sb, flagsb, solomon islands flag" + case .flagSc: return "flag-sc, flagsc, seychelles flag" case .flagSd: return "flag-sd, flagsd, sudan flag" - case .flagSe: return "flag-se, sweden flag, flagse" - case .flagSg: return "flag-sg, singapore flag, flagsg" - case .flagSh: return "flagsh, st. helena flag, flag-sh" - case .flagSi: return "flag-si, slovenia flag, flagsi" - case .flagSj: return "flag-sj, svalbard & jan mayen flag, flagsj" - case .flagSk: return "slovakia flag, flagsk, flag-sk" - case .flagSl: return "sierra leone flag, flag-sl, flagsl" - case .flagSm: return "flag-sm, san marino flag, flagsm" - case .flagSn: return "senegal flag, flagsn, flag-sn" - case .flagSo: return "flagso, flag-so, somalia flag" - case .flagSr: return "flag-sr, suriname flag, flagsr" - case .flagSs: return "flag-ss, south sudan flag, flagss" - case .flagSt: return "flagst, flag-st, são tomé & príncipe flag" - case .flagSv: return "flag-sv, el salvador flag, flagsv" - case .flagSx: return "flag-sx, sint maarten flag, flagsx" - case .flagSy: return "flag-sy, syria flag, flagsy" - case .flagSz: return "flagsz, eswatini flag, flag-sz" + case .flagSe: return "flag-se, flagse, sweden flag" + case .flagSg: return "flag-sg, flagsg, singapore flag" + case .flagSh: return "flag-sh, flagsh, st. helena flag" + case .flagSi: return "flag-si, flagsi, slovenia flag" + case .flagSj: return "flag-sj, flagsj, svalbard & jan mayen flag" + case .flagSk: return "flag-sk, flagsk, slovakia flag" + case .flagSl: return "flag-sl, flagsl, sierra leone flag" + case .flagSm: return "flag-sm, flagsm, san marino flag" + case .flagSn: return "flag-sn, flagsn, senegal flag" + case .flagSo: return "flag-so, flagso, somalia flag" + case .flagSr: return "flag-sr, flagsr, suriname flag" + case .flagSs: return "flag-ss, flagss, south sudan flag" + case .flagSt: return "flag-st, flagst, são tomé & príncipe flag" + case .flagSv: return "el salvador flag, flag-sv, flagsv" + case .flagSx: return "flag-sx, flagsx, sint maarten flag" + case .flagSy: return "flag-sy, flagsy, syria flag" + case .flagSz: return "eswatini flag, flag-sz, flagsz" case .flagTa: return "flag-ta, flagta, tristan da cunha flag" - case .flagTc: return "flagtc, turks & caicos islands flag, flag-tc" - case .flagTd: return "flagtd, flag-td, chad flag" - case .flagTf: return "flag-tf, french southern territories flag, flagtf" - case .flagTg: return "flagtg, togo flag, flag-tg" - case .flagTh: return "thailand flag, flagth, flag-th" - case .flagTj: return "tajikistan flag, flagtj, flag-tj" - case .flagTk: return "tokelau flag, flag-tk, flagtk" - case .flagTl: return "flag-tl, timor-leste flag, flagtl" - case .flagTm: return "flag-tm, turkmenistan flag, flagtm" - case .flagTn: return "flagtn, tunisia flag, flag-tn" + case .flagTc: return "flag-tc, flagtc, turks & caicos islands flag" + case .flagTd: return "chad flag, flag-td, flagtd" + case .flagTf: return "flag-tf, flagtf, french southern territories flag" + case .flagTg: return "flag-tg, flagtg, togo flag" + case .flagTh: return "flag-th, flagth, thailand flag" + case .flagTj: return "flag-tj, flagtj, tajikistan flag" + case .flagTk: return "flag-tk, flagtk, tokelau flag" + case .flagTl: return "flag-tl, flagtl, timor-leste flag" + case .flagTm: return "flag-tm, flagtm, turkmenistan flag" + case .flagTn: return "flag-tn, flagtn, tunisia flag" case .flagTo: return "flag-to, flagto, tonga flag" - case .flagTr: return "flagtr, flag-tr, turkey flag" - case .flagTt: return "flag-tt, trinidad & tobago flag, flagtt" - case .flagTv: return "tuvalu flag, flag-tv, flagtv" - case .flagTw: return "flag-tw, taiwan flag, flagtw" + case .flagTr: return "flag-tr, flagtr, turkey flag" + case .flagTt: return "flag-tt, flagtt, trinidad & tobago flag" + case .flagTv: return "flag-tv, flagtv, tuvalu flag" + case .flagTw: return "flag-tw, flagtw, taiwan flag" case .flagTz: return "flag-tz, flagtz, tanzania flag" - case .flagUa: return "ukraine flag, flagua, flag-ua" - case .flagUg: return "flagug, uganda flag, flag-ug" + case .flagUa: return "flag-ua, flagua, ukraine flag" + case .flagUg: return "flag-ug, flagug, uganda flag" case .flagUm: return "flag-um, flagum, u.s. outlying islands flag" - case .flagUn: return "united nations flag, flag-un, flagun" - case .us: return "flag-us, us, united states flag" - case .flagUy: return "flaguy, uruguay flag, flag-uy" - case .flagUz: return "flag-uz, uzbekistan flag, flaguz" + case .flagUn: return "flag-un, flagun, united nations flag" + case .us: return "flag-us, united states flag, us" + case .flagUy: return "flag-uy, flaguy, uruguay flag" + case .flagUz: return "flag-uz, flaguz, uzbekistan flag" case .flagVa: return "flag-va, flagva, vatican city flag" - case .flagVc: return "flag-vc, st. vincent & grenadines flag, flagvc" - case .flagVe: return "flag-ve, venezuela flag, flagve" - case .flagVg: return "flag-vg, flagvg, british virgin islands flag" - case .flagVi: return "flagvi, u.s. virgin islands flag, flag-vi" - case .flagVn: return "flagvn, flag-vn, vietnam flag" - case .flagVu: return "flagvu, vanuatu flag, flag-vu" - case .flagWf: return "flag-wf, wallis & futuna flag, flagwf" - case .flagWs: return "flag-ws, samoa flag, flagws" - case .flagXk: return "flagxk, kosovo flag, flag-xk" - case .flagYe: return "flagye, yemen flag, flag-ye" + case .flagVc: return "flag-vc, flagvc, st. vincent & grenadines flag" + case .flagVe: return "flag-ve, flagve, venezuela flag" + case .flagVg: return "british virgin islands flag, flag-vg, flagvg" + case .flagVi: return "flag-vi, flagvi, u.s. virgin islands flag" + case .flagVn: return "flag-vn, flagvn, vietnam flag" + case .flagVu: return "flag-vu, flagvu, vanuatu flag" + case .flagWf: return "flag-wf, flagwf, wallis & futuna flag" + case .flagWs: return "flag-ws, flagws, samoa flag" + case .flagXk: return "flag-xk, flagxk, kosovo flag" + case .flagYe: return "flag-ye, flagye, yemen flag" case .flagYt: return "flag-yt, flagyt, mayotte flag" - case .flagZa: return "south africa flag, flagza, flag-za" - case .flagZm: return "flag-zm, zambia flag, flagzm" - case .flagZw: return "flagzw, zimbabwe flag, flag-zw" - case .flagEngland: return "flagengland, england flag, flag-england" - case .flagScotland: return "scotland flag, flagscotland, flag-scotland" - case .flagWales: return "flagwales, flag-wales, wales flag" + case .flagZa: return "flag-za, flagza, south africa flag" + case .flagZm: return "flag-zm, flagzm, zambia flag" + case .flagZw: return "flag-zw, flagzw, zimbabwe flag" + case .flagEngland: return "england flag, flag-england, flagengland" + case .flagScotland: return "flag-scotland, flagscotland, scotland flag" + case .flagWales: return "flag-wales, flagwales, wales flag" } } } diff --git a/Session/Emoji/Emoji+SkinTones.swift b/Session/Emoji/Emoji+SkinTones.swift index e9aaec044..facfd6d44 100644 --- a/Session/Emoji/Emoji+SkinTones.swift +++ b/Session/Emoji/Emoji+SkinTones.swift @@ -106,6 +106,22 @@ extension Emoji { [.mediumDark]: "🫴🏾", [.dark]: "🫴🏿", ] + case .leftwardsPushingHand: + return [ + [.light]: "🫷🏻", + [.mediumLight]: "🫷🏼", + [.medium]: "🫷🏽", + [.mediumDark]: "🫷🏾", + [.dark]: "🫷🏿", + ] + case .rightwardsPushingHand: + return [ + [.light]: "🫸🏻", + [.mediumLight]: "🫸🏼", + [.medium]: "🫸🏽", + [.mediumDark]: "🫸🏾", + [.dark]: "🫸🏿", + ] case .okHand: return [ [.light]: "👌🏻", diff --git a/Session/Emoji/Emoji.swift b/Session/Emoji/Emoji.swift index 096dae434..15cda7df5 100644 --- a/Session/Emoji/Emoji.swift +++ b/Session/Emoji/Emoji.swift @@ -54,6 +54,7 @@ enum Emoji: String, CaseIterable, Equatable { case grimacing = "😬" case faceExhaling = "😮‍💨" case lyingFace = "🤥" + case shakingFace = "🫨" case relieved = "😌" case pensive = "😔" case sleepy = "😪" @@ -131,7 +132,6 @@ enum Emoji: String, CaseIterable, Equatable { case seeNoEvil = "🙈" case hearNoEvil = "🙉" case speakNoEvil = "🙊" - case kiss = "💋" case loveLetter = "💌" case cupid = "💘" case giftHeart = "💝" @@ -146,14 +146,18 @@ enum Emoji: String, CaseIterable, Equatable { case heartOnFire = "❤️‍🔥" case mendingHeart = "❤️‍🩹" case heart = "❤️" + case pinkHeart = "🩷" case orangeHeart = "🧡" case yellowHeart = "💛" case greenHeart = "💚" case blueHeart = "💙" + case lightBlueHeart = "🩵" case purpleHeart = "💜" case brownHeart = "🤎" case blackHeart = "🖤" + case greyHeart = "🩶" case whiteHeart = "🤍" + case kiss = "💋" case oneHundred = "💯" case anger = "💢" case boom = "💥" @@ -161,7 +165,6 @@ enum Emoji: String, CaseIterable, Equatable { case sweatDrops = "💦" case dash = "💨" case hole = "🕳️" - case bomb = "💣" case speechBalloon = "💬" case eyeInSpeechBubble = "👁️‍🗨️" case leftSpeechBubble = "🗨️" @@ -177,6 +180,8 @@ enum Emoji: String, CaseIterable, Equatable { case leftwardsHand = "🫲" case palmDownHand = "🫳" case palmUpHand = "🫴" + case leftwardsPushingHand = "🫷" + case rightwardsPushingHand = "🫸" case okHand = "👌" case pinchedFingers = "🤌" case pinchingHand = "🤏" @@ -554,6 +559,8 @@ enum Emoji: String, CaseIterable, Equatable { case tiger2 = "🐅" case leopard = "🐆" case horse = "🐴" + case moose = "🫎" + case donkey = "🫏" case racehorse = "🐎" case unicornFace = "🦄" case zebraFace = "🦓" @@ -616,6 +623,9 @@ enum Emoji: String, CaseIterable, Equatable { case flamingo = "🦩" case peacock = "🦚" case parrot = "🦜" + case wing = "🪽" + case blackBird = "🐦‍⬛" + case goose = "🪿" case frog = "🐸" case crocodile = "🐊" case turtle = "🐢" @@ -636,6 +646,7 @@ enum Emoji: String, CaseIterable, Equatable { case octopus = "🐙" case shell = "🐚" case coral = "🪸" + case jellyfish = "🪼" case snail = "🐌" case butterfly = "🦋" case bug = "🐛" @@ -663,6 +674,7 @@ enum Emoji: String, CaseIterable, Equatable { case sunflower = "🌻" case blossom = "🌼" case tulip = "🌷" + case hyacinth = "🪻" case seedling = "🌱" case pottedPlant = "🪴" case evergreenTree = "🌲" @@ -678,6 +690,7 @@ enum Emoji: String, CaseIterable, Equatable { case leaves = "🍃" case emptyNest = "🪹" case nestWithEggs = "🪺" + case mushroom = "🍄" case grapes = "🍇" case melon = "🍈" case watermelon = "🍉" @@ -709,10 +722,11 @@ enum Emoji: String, CaseIterable, Equatable { case broccoli = "🥦" case garlic = "🧄" case onion = "🧅" - case mushroom = "🍄" case peanuts = "🥜" case beans = "🫘" case chestnut = "🌰" + case gingerRoot = "🫚" + case peaPod = "🫛" case bread = "🍞" case croissant = "🥐" case baguetteBread = "🥖" @@ -1085,11 +1099,10 @@ enum Emoji: String, CaseIterable, Equatable { case dart = "🎯" case yoYo = "🪀" case kite = "🪁" + case gun = "🔫" case eightBall = "🎱" case crystalBall = "🔮" case magicWand = "🪄" - case nazarAmulet = "🧿" - case hamsa = "🪬" case videoGame = "🎮" case joystick = "🕹️" case slotMachine = "🎰" @@ -1134,6 +1147,7 @@ enum Emoji: String, CaseIterable, Equatable { case shorts = "🩳" case bikini = "👙" case womansClothes = "👚" + case foldingHandFan = "🪭" case purse = "👛" case handbag = "👜" case pouch = "👝" @@ -1148,6 +1162,7 @@ enum Emoji: String, CaseIterable, Equatable { case sandal = "👡" case balletShoes = "🩰" case boot = "👢" + case hairPick = "🪮" case crown = "👑" case womansHat = "👒" case tophat = "🎩" @@ -1186,6 +1201,8 @@ enum Emoji: String, CaseIterable, Equatable { case banjo = "🪕" case drumWithDrumsticks = "🥁" case longDrum = "🪘" + case maracas = "🪇" + case flute = "🪈" case iphone = "📱" case calling = "📲" case phone = "☎️" @@ -1305,7 +1322,7 @@ enum Emoji: String, CaseIterable, Equatable { case hammerAndWrench = "🛠️" case daggerKnife = "🗡️" case crossedSwords = "⚔️" - case gun = "🔫" + case bomb = "💣" case boomerang = "🪃" case bowAndArrow = "🏹" case shield = "🛡️" @@ -1366,6 +1383,8 @@ enum Emoji: String, CaseIterable, Equatable { case coffin = "⚰️" case headstone = "🪦" case funeralUrn = "⚱️" + case nazarAmulet = "🧿" + case hamsa = "🪬" case moyai = "🗿" case placard = "🪧" case identificationCard = "🪪" @@ -1428,6 +1447,7 @@ enum Emoji: String, CaseIterable, Equatable { case peaceSymbol = "☮️" case menorahWithNineBranches = "🕎" case sixPointedStar = "🔯" + case khanda = "🪯" case aries = "♈" case taurus = "♉" case gemini = "♊" @@ -1463,6 +1483,7 @@ enum Emoji: String, CaseIterable, Equatable { case lowBrightness = "🔅" case highBrightness = "🔆" case signalStrength = "📶" + case wireless = "🛜" case vibrationMode = "📳" case mobilePhoneOff = "📴" case femaleSign = "♀️" diff --git a/Session/Emoji/EmojiWithSkinTones+String.swift b/Session/Emoji/EmojiWithSkinTones+String.swift index 218048c86..8f4bd5d74 100644 --- a/Session/Emoji/EmojiWithSkinTones+String.swift +++ b/Session/Emoji/EmojiWithSkinTones+String.swift @@ -4,7266 +4,4725 @@ extension EmojiWithSkinTones { init?(rawValue: String) { guard rawValue.isSingleEmoji else { return nil } - if rawValue == "😀" { - self.init(baseEmoji: .grinning, skinTones: nil) - } else if rawValue == "😃" { - self.init(baseEmoji: .smiley, skinTones: nil) - } else if rawValue == "😄" { - self.init(baseEmoji: .smile, skinTones: nil) - } else if rawValue == "😁" { - self.init(baseEmoji: .grin, skinTones: nil) - } else if rawValue == "😆" { - self.init(baseEmoji: .laughing, skinTones: nil) - } else if rawValue == "😅" { - self.init(baseEmoji: .sweatSmile, skinTones: nil) - } else if rawValue == "🤣" { - self.init(baseEmoji: .rollingOnTheFloorLaughing, skinTones: nil) - } else if rawValue == "😂" { - self.init(baseEmoji: .joy, skinTones: nil) - } else if rawValue == "🙂" { - self.init(baseEmoji: .slightlySmilingFace, skinTones: nil) - } else if rawValue == "🙃" { - self.init(baseEmoji: .upsideDownFace, skinTones: nil) - } else if rawValue == "🫠" { - self.init(baseEmoji: .meltingFace, skinTones: nil) - } else if rawValue == "😉" { - self.init(baseEmoji: .wink, skinTones: nil) - } else if rawValue == "😊" { - self.init(baseEmoji: .blush, skinTones: nil) - } else if rawValue == "😇" { - self.init(baseEmoji: .innocent, skinTones: nil) - } else if rawValue == "🥰" { - self.init(baseEmoji: .smilingFaceWith3Hearts, skinTones: nil) - } else if rawValue == "😍" { - self.init(baseEmoji: .heartEyes, skinTones: nil) - } else if rawValue == "🤩" { - self.init(baseEmoji: .starStruck, skinTones: nil) - } else if rawValue == "😘" { - self.init(baseEmoji: .kissingHeart, skinTones: nil) - } else if rawValue == "😗" { - self.init(baseEmoji: .kissing, skinTones: nil) - } else if rawValue == "☺️" { - self.init(baseEmoji: .relaxed, skinTones: nil) - } else if rawValue == "😚" { - self.init(baseEmoji: .kissingClosedEyes, skinTones: nil) - } else if rawValue == "😙" { - self.init(baseEmoji: .kissingSmilingEyes, skinTones: nil) - } else if rawValue == "🥲" { - self.init(baseEmoji: .smilingFaceWithTear, skinTones: nil) - } else if rawValue == "😋" { - self.init(baseEmoji: .yum, skinTones: nil) - } else if rawValue == "😛" { - self.init(baseEmoji: .stuckOutTongue, skinTones: nil) - } else if rawValue == "😜" { - self.init(baseEmoji: .stuckOutTongueWinkingEye, skinTones: nil) - } else if rawValue == "🤪" { - self.init(baseEmoji: .zanyFace, skinTones: nil) - } else if rawValue == "😝" { - self.init(baseEmoji: .stuckOutTongueClosedEyes, skinTones: nil) - } else if rawValue == "🤑" { - self.init(baseEmoji: .moneyMouthFace, skinTones: nil) - } else if rawValue == "🤗" { - self.init(baseEmoji: .huggingFace, skinTones: nil) - } else if rawValue == "🤭" { - self.init(baseEmoji: .faceWithHandOverMouth, skinTones: nil) - } else if rawValue == "🫢" { - self.init(baseEmoji: .faceWithOpenEyesAndHandOverMouth, skinTones: nil) - } else if rawValue == "🫣" { - self.init(baseEmoji: .faceWithPeekingEye, skinTones: nil) - } else if rawValue == "🤫" { - self.init(baseEmoji: .shushingFace, skinTones: nil) - } else if rawValue == "🤔" { - self.init(baseEmoji: .thinkingFace, skinTones: nil) - } else if rawValue == "🫡" { - self.init(baseEmoji: .salutingFace, skinTones: nil) - } else if rawValue == "🤐" { - self.init(baseEmoji: .zipperMouthFace, skinTones: nil) - } else if rawValue == "🤨" { - self.init(baseEmoji: .faceWithRaisedEyebrow, skinTones: nil) - } else if rawValue == "😐" { - self.init(baseEmoji: .neutralFace, skinTones: nil) - } else if rawValue == "😑" { - self.init(baseEmoji: .expressionless, skinTones: nil) - } else if rawValue == "😶" { - self.init(baseEmoji: .noMouth, skinTones: nil) - } else if rawValue == "🫥" { - self.init(baseEmoji: .dottedLineFace, skinTones: nil) - } else if rawValue == "😶‍🌫️" { - self.init(baseEmoji: .faceInClouds, skinTones: nil) - } else if rawValue == "😏" { - self.init(baseEmoji: .smirk, skinTones: nil) - } else if rawValue == "😒" { - self.init(baseEmoji: .unamused, skinTones: nil) - } else if rawValue == "🙄" { - self.init(baseEmoji: .faceWithRollingEyes, skinTones: nil) - } else if rawValue == "😬" { - self.init(baseEmoji: .grimacing, skinTones: nil) - } else if rawValue == "😮‍💨" { - self.init(baseEmoji: .faceExhaling, skinTones: nil) - } else if rawValue == "🤥" { - self.init(baseEmoji: .lyingFace, skinTones: nil) - } else if rawValue == "😌" { - self.init(baseEmoji: .relieved, skinTones: nil) - } else if rawValue == "😔" { - self.init(baseEmoji: .pensive, skinTones: nil) - } else if rawValue == "😪" { - self.init(baseEmoji: .sleepy, skinTones: nil) - } else if rawValue == "🤤" { - self.init(baseEmoji: .droolingFace, skinTones: nil) - } else if rawValue == "😴" { - self.init(baseEmoji: .sleeping, skinTones: nil) - } else if rawValue == "😷" { - self.init(baseEmoji: .mask, skinTones: nil) - } else if rawValue == "🤒" { - self.init(baseEmoji: .faceWithThermometer, skinTones: nil) - } else if rawValue == "🤕" { - self.init(baseEmoji: .faceWithHeadBandage, skinTones: nil) - } else if rawValue == "🤢" { - self.init(baseEmoji: .nauseatedFace, skinTones: nil) - } else if rawValue == "🤮" { - self.init(baseEmoji: .faceVomiting, skinTones: nil) - } else if rawValue == "🤧" { - self.init(baseEmoji: .sneezingFace, skinTones: nil) - } else if rawValue == "🥵" { - self.init(baseEmoji: .hotFace, skinTones: nil) - } else if rawValue == "🥶" { - self.init(baseEmoji: .coldFace, skinTones: nil) - } else if rawValue == "🥴" { - self.init(baseEmoji: .woozyFace, skinTones: nil) - } else if rawValue == "😵" { - self.init(baseEmoji: .dizzyFace, skinTones: nil) - } else if rawValue == "😵‍💫" { - self.init(baseEmoji: .faceWithSpiralEyes, skinTones: nil) - } else if rawValue == "🤯" { - self.init(baseEmoji: .explodingHead, skinTones: nil) - } else if rawValue == "🤠" { - self.init(baseEmoji: .faceWithCowboyHat, skinTones: nil) - } else if rawValue == "🥳" { - self.init(baseEmoji: .partyingFace, skinTones: nil) - } else if rawValue == "🥸" { - self.init(baseEmoji: .disguisedFace, skinTones: nil) - } else if rawValue == "😎" { - self.init(baseEmoji: .sunglasses, skinTones: nil) - } else if rawValue == "🤓" { - self.init(baseEmoji: .nerdFace, skinTones: nil) - } else if rawValue == "🧐" { - self.init(baseEmoji: .faceWithMonocle, skinTones: nil) - } else if rawValue == "😕" { - self.init(baseEmoji: .confused, skinTones: nil) - } else if rawValue == "🫤" { - self.init(baseEmoji: .faceWithDiagonalMouth, skinTones: nil) - } else if rawValue == "😟" { - self.init(baseEmoji: .worried, skinTones: nil) - } else if rawValue == "🙁" { - self.init(baseEmoji: .slightlyFrowningFace, skinTones: nil) - } else if rawValue == "☹️" { - self.init(baseEmoji: .whiteFrowningFace, skinTones: nil) - } else if rawValue == "😮" { - self.init(baseEmoji: .openMouth, skinTones: nil) - } else if rawValue == "😯" { - self.init(baseEmoji: .hushed, skinTones: nil) - } else if rawValue == "😲" { - self.init(baseEmoji: .astonished, skinTones: nil) - } else if rawValue == "😳" { - self.init(baseEmoji: .flushed, skinTones: nil) - } else if rawValue == "🥺" { - self.init(baseEmoji: .pleadingFace, skinTones: nil) - } else if rawValue == "🥹" { - self.init(baseEmoji: .faceHoldingBackTears, skinTones: nil) - } else if rawValue == "😦" { - self.init(baseEmoji: .frowning, skinTones: nil) - } else if rawValue == "😧" { - self.init(baseEmoji: .anguished, skinTones: nil) - } else if rawValue == "😨" { - self.init(baseEmoji: .fearful, skinTones: nil) - } else if rawValue == "😰" { - self.init(baseEmoji: .coldSweat, skinTones: nil) - } else if rawValue == "😥" { - self.init(baseEmoji: .disappointedRelieved, skinTones: nil) - } else if rawValue == "😢" { - self.init(baseEmoji: .cry, skinTones: nil) - } else if rawValue == "😭" { - self.init(baseEmoji: .sob, skinTones: nil) - } else if rawValue == "😱" { - self.init(baseEmoji: .scream, skinTones: nil) - } else if rawValue == "😖" { - self.init(baseEmoji: .confounded, skinTones: nil) - } else if rawValue == "😣" { - self.init(baseEmoji: .persevere, skinTones: nil) - } else if rawValue == "😞" { - self.init(baseEmoji: .disappointed, skinTones: nil) - } else if rawValue == "😓" { - self.init(baseEmoji: .sweat, skinTones: nil) - } else if rawValue == "😩" { - self.init(baseEmoji: .weary, skinTones: nil) - } else if rawValue == "😫" { - self.init(baseEmoji: .tiredFace, skinTones: nil) - } else if rawValue == "🥱" { - self.init(baseEmoji: .yawningFace, skinTones: nil) - } else if rawValue == "😤" { - self.init(baseEmoji: .triumph, skinTones: nil) - } else if rawValue == "😡" { - self.init(baseEmoji: .rage, skinTones: nil) - } else if rawValue == "😠" { - self.init(baseEmoji: .angry, skinTones: nil) - } else if rawValue == "🤬" { - self.init(baseEmoji: .faceWithSymbolsOnMouth, skinTones: nil) - } else if rawValue == "😈" { - self.init(baseEmoji: .smilingImp, skinTones: nil) - } else if rawValue == "👿" { - self.init(baseEmoji: .imp, skinTones: nil) - } else if rawValue == "💀" { - self.init(baseEmoji: .skull, skinTones: nil) - } else if rawValue == "☠️" { - self.init(baseEmoji: .skullAndCrossbones, skinTones: nil) - } else if rawValue == "💩" { - self.init(baseEmoji: .hankey, skinTones: nil) - } else if rawValue == "🤡" { - self.init(baseEmoji: .clownFace, skinTones: nil) - } else if rawValue == "👹" { - self.init(baseEmoji: .japaneseOgre, skinTones: nil) - } else if rawValue == "👺" { - self.init(baseEmoji: .japaneseGoblin, skinTones: nil) - } else if rawValue == "👻" { - self.init(baseEmoji: .ghost, skinTones: nil) - } else if rawValue == "👽" { - self.init(baseEmoji: .alien, skinTones: nil) - } else if rawValue == "👾" { - self.init(baseEmoji: .spaceInvader, skinTones: nil) - } else if rawValue == "🤖" { - self.init(baseEmoji: .robotFace, skinTones: nil) - } else if rawValue == "😺" { - self.init(baseEmoji: .smileyCat, skinTones: nil) - } else if rawValue == "😸" { - self.init(baseEmoji: .smileCat, skinTones: nil) - } else if rawValue == "😹" { - self.init(baseEmoji: .joyCat, skinTones: nil) - } else if rawValue == "😻" { - self.init(baseEmoji: .heartEyesCat, skinTones: nil) - } else if rawValue == "😼" { - self.init(baseEmoji: .smirkCat, skinTones: nil) - } else if rawValue == "😽" { - self.init(baseEmoji: .kissingCat, skinTones: nil) - } else if rawValue == "🙀" { - self.init(baseEmoji: .screamCat, skinTones: nil) - } else if rawValue == "😿" { - self.init(baseEmoji: .cryingCatFace, skinTones: nil) - } else if rawValue == "😾" { - self.init(baseEmoji: .poutingCat, skinTones: nil) - } else if rawValue == "🙈" { - self.init(baseEmoji: .seeNoEvil, skinTones: nil) - } else if rawValue == "🙉" { - self.init(baseEmoji: .hearNoEvil, skinTones: nil) - } else if rawValue == "🙊" { - self.init(baseEmoji: .speakNoEvil, skinTones: nil) - } else if rawValue == "💋" { - self.init(baseEmoji: .kiss, skinTones: nil) - } else if rawValue == "💌" { - self.init(baseEmoji: .loveLetter, skinTones: nil) - } else if rawValue == "💘" { - self.init(baseEmoji: .cupid, skinTones: nil) - } else if rawValue == "💝" { - self.init(baseEmoji: .giftHeart, skinTones: nil) - } else if rawValue == "💖" { - self.init(baseEmoji: .sparklingHeart, skinTones: nil) - } else if rawValue == "💗" { - self.init(baseEmoji: .heartpulse, skinTones: nil) - } else if rawValue == "💓" { - self.init(baseEmoji: .heartbeat, skinTones: nil) - } else if rawValue == "💞" { - self.init(baseEmoji: .revolvingHearts, skinTones: nil) - } else if rawValue == "💕" { - self.init(baseEmoji: .twoHearts, skinTones: nil) - } else if rawValue == "💟" { - self.init(baseEmoji: .heartDecoration, skinTones: nil) - } else if rawValue == "❣️" { - self.init(baseEmoji: .heavyHeartExclamationMarkOrnament, skinTones: nil) - } else if rawValue == "💔" { - self.init(baseEmoji: .brokenHeart, skinTones: nil) - } else if rawValue == "❤️‍🔥" { - self.init(baseEmoji: .heartOnFire, skinTones: nil) - } else if rawValue == "❤️‍🩹" { - self.init(baseEmoji: .mendingHeart, skinTones: nil) - } else if rawValue == "❤️" { - self.init(baseEmoji: .heart, skinTones: nil) - } else if rawValue == "🧡" { - self.init(baseEmoji: .orangeHeart, skinTones: nil) - } else if rawValue == "💛" { - self.init(baseEmoji: .yellowHeart, skinTones: nil) - } else if rawValue == "💚" { - self.init(baseEmoji: .greenHeart, skinTones: nil) - } else if rawValue == "💙" { - self.init(baseEmoji: .blueHeart, skinTones: nil) - } else if rawValue == "💜" { - self.init(baseEmoji: .purpleHeart, skinTones: nil) - } else if rawValue == "🤎" { - self.init(baseEmoji: .brownHeart, skinTones: nil) - } else if rawValue == "🖤" { - self.init(baseEmoji: .blackHeart, skinTones: nil) - } else if rawValue == "🤍" { - self.init(baseEmoji: .whiteHeart, skinTones: nil) - } else if rawValue == "💯" { - self.init(baseEmoji: .oneHundred, skinTones: nil) - } else if rawValue == "💢" { - self.init(baseEmoji: .anger, skinTones: nil) - } else if rawValue == "💥" { - self.init(baseEmoji: .boom, skinTones: nil) - } else if rawValue == "💫" { - self.init(baseEmoji: .dizzy, skinTones: nil) - } else if rawValue == "💦" { - self.init(baseEmoji: .sweatDrops, skinTones: nil) - } else if rawValue == "💨" { - self.init(baseEmoji: .dash, skinTones: nil) - } else if rawValue == "🕳️" { - self.init(baseEmoji: .hole, skinTones: nil) - } else if rawValue == "💣" { - self.init(baseEmoji: .bomb, skinTones: nil) - } else if rawValue == "💬" { - self.init(baseEmoji: .speechBalloon, skinTones: nil) - } else if rawValue == "👁️‍🗨️" { - self.init(baseEmoji: .eyeInSpeechBubble, skinTones: nil) - } else if rawValue == "🗨️" { - self.init(baseEmoji: .leftSpeechBubble, skinTones: nil) - } else if rawValue == "🗯️" { - self.init(baseEmoji: .rightAngerBubble, skinTones: nil) - } else if rawValue == "💭" { - self.init(baseEmoji: .thoughtBalloon, skinTones: nil) - } else if rawValue == "💤" { - self.init(baseEmoji: .zzz, skinTones: nil) - } else if rawValue == "👋" { - self.init(baseEmoji: .wave, skinTones: nil) - } else if rawValue == "👋🏻" { - self.init(baseEmoji: .wave, skinTones: [.light]) - } else if rawValue == "👋🏼" { - self.init(baseEmoji: .wave, skinTones: [.mediumLight]) - } else if rawValue == "👋🏽" { - self.init(baseEmoji: .wave, skinTones: [.medium]) - } else if rawValue == "👋🏾" { - self.init(baseEmoji: .wave, skinTones: [.mediumDark]) - } else if rawValue == "👋🏿" { - self.init(baseEmoji: .wave, skinTones: [.dark]) - } else if rawValue == "🤚" { - self.init(baseEmoji: .raisedBackOfHand, skinTones: nil) - } else if rawValue == "🤚🏻" { - self.init(baseEmoji: .raisedBackOfHand, skinTones: [.light]) - } else if rawValue == "🤚🏼" { - self.init(baseEmoji: .raisedBackOfHand, skinTones: [.mediumLight]) - } else if rawValue == "🤚🏽" { - self.init(baseEmoji: .raisedBackOfHand, skinTones: [.medium]) - } else if rawValue == "🤚🏾" { - self.init(baseEmoji: .raisedBackOfHand, skinTones: [.mediumDark]) - } else if rawValue == "🤚🏿" { - self.init(baseEmoji: .raisedBackOfHand, skinTones: [.dark]) - } else if rawValue == "🖐️" { - self.init(baseEmoji: .raisedHandWithFingersSplayed, skinTones: nil) - } else if rawValue == "🖐🏻" { - self.init(baseEmoji: .raisedHandWithFingersSplayed, skinTones: [.light]) - } else if rawValue == "🖐🏼" { - self.init(baseEmoji: .raisedHandWithFingersSplayed, skinTones: [.mediumLight]) - } else if rawValue == "🖐🏽" { - self.init(baseEmoji: .raisedHandWithFingersSplayed, skinTones: [.medium]) - } else if rawValue == "🖐🏾" { - self.init(baseEmoji: .raisedHandWithFingersSplayed, skinTones: [.mediumDark]) - } else if rawValue == "🖐🏿" { - self.init(baseEmoji: .raisedHandWithFingersSplayed, skinTones: [.dark]) - } else if rawValue == "✋" { - self.init(baseEmoji: .hand, skinTones: nil) - } else if rawValue == "✋🏻" { - self.init(baseEmoji: .hand, skinTones: [.light]) - } else if rawValue == "✋🏼" { - self.init(baseEmoji: .hand, skinTones: [.mediumLight]) - } else if rawValue == "✋🏽" { - self.init(baseEmoji: .hand, skinTones: [.medium]) - } else if rawValue == "✋🏾" { - self.init(baseEmoji: .hand, skinTones: [.mediumDark]) - } else if rawValue == "✋🏿" { - self.init(baseEmoji: .hand, skinTones: [.dark]) - } else if rawValue == "🖖" { - self.init(baseEmoji: .spockHand, skinTones: nil) - } else if rawValue == "🖖🏻" { - self.init(baseEmoji: .spockHand, skinTones: [.light]) - } else if rawValue == "🖖🏼" { - self.init(baseEmoji: .spockHand, skinTones: [.mediumLight]) - } else if rawValue == "🖖🏽" { - self.init(baseEmoji: .spockHand, skinTones: [.medium]) - } else if rawValue == "🖖🏾" { - self.init(baseEmoji: .spockHand, skinTones: [.mediumDark]) - } else if rawValue == "🖖🏿" { - self.init(baseEmoji: .spockHand, skinTones: [.dark]) - } else if rawValue == "🫱" { - self.init(baseEmoji: .rightwardsHand, skinTones: nil) - } else if rawValue == "🫱🏻" { - self.init(baseEmoji: .rightwardsHand, skinTones: [.light]) - } else if rawValue == "🫱🏼" { - self.init(baseEmoji: .rightwardsHand, skinTones: [.mediumLight]) - } else if rawValue == "🫱🏽" { - self.init(baseEmoji: .rightwardsHand, skinTones: [.medium]) - } else if rawValue == "🫱🏾" { - self.init(baseEmoji: .rightwardsHand, skinTones: [.mediumDark]) - } else if rawValue == "🫱🏿" { - self.init(baseEmoji: .rightwardsHand, skinTones: [.dark]) - } else if rawValue == "🫲" { - self.init(baseEmoji: .leftwardsHand, skinTones: nil) - } else if rawValue == "🫲🏻" { - self.init(baseEmoji: .leftwardsHand, skinTones: [.light]) - } else if rawValue == "🫲🏼" { - self.init(baseEmoji: .leftwardsHand, skinTones: [.mediumLight]) - } else if rawValue == "🫲🏽" { - self.init(baseEmoji: .leftwardsHand, skinTones: [.medium]) - } else if rawValue == "🫲🏾" { - self.init(baseEmoji: .leftwardsHand, skinTones: [.mediumDark]) - } else if rawValue == "🫲🏿" { - self.init(baseEmoji: .leftwardsHand, skinTones: [.dark]) - } else if rawValue == "🫳" { - self.init(baseEmoji: .palmDownHand, skinTones: nil) - } else if rawValue == "🫳🏻" { - self.init(baseEmoji: .palmDownHand, skinTones: [.light]) - } else if rawValue == "🫳🏼" { - self.init(baseEmoji: .palmDownHand, skinTones: [.mediumLight]) - } else if rawValue == "🫳🏽" { - self.init(baseEmoji: .palmDownHand, skinTones: [.medium]) - } else if rawValue == "🫳🏾" { - self.init(baseEmoji: .palmDownHand, skinTones: [.mediumDark]) - } else if rawValue == "🫳🏿" { - self.init(baseEmoji: .palmDownHand, skinTones: [.dark]) - } else if rawValue == "🫴" { - self.init(baseEmoji: .palmUpHand, skinTones: nil) - } else if rawValue == "🫴🏻" { - self.init(baseEmoji: .palmUpHand, skinTones: [.light]) - } else if rawValue == "🫴🏼" { - self.init(baseEmoji: .palmUpHand, skinTones: [.mediumLight]) - } else if rawValue == "🫴🏽" { - self.init(baseEmoji: .palmUpHand, skinTones: [.medium]) - } else if rawValue == "🫴🏾" { - self.init(baseEmoji: .palmUpHand, skinTones: [.mediumDark]) - } else if rawValue == "🫴🏿" { - self.init(baseEmoji: .palmUpHand, skinTones: [.dark]) - } else if rawValue == "👌" { - self.init(baseEmoji: .okHand, skinTones: nil) - } else if rawValue == "👌🏻" { - self.init(baseEmoji: .okHand, skinTones: [.light]) - } else if rawValue == "👌🏼" { - self.init(baseEmoji: .okHand, skinTones: [.mediumLight]) - } else if rawValue == "👌🏽" { - self.init(baseEmoji: .okHand, skinTones: [.medium]) - } else if rawValue == "👌🏾" { - self.init(baseEmoji: .okHand, skinTones: [.mediumDark]) - } else if rawValue == "👌🏿" { - self.init(baseEmoji: .okHand, skinTones: [.dark]) - } else if rawValue == "🤌" { - self.init(baseEmoji: .pinchedFingers, skinTones: nil) - } else if rawValue == "🤌🏻" { - self.init(baseEmoji: .pinchedFingers, skinTones: [.light]) - } else if rawValue == "🤌🏼" { - self.init(baseEmoji: .pinchedFingers, skinTones: [.mediumLight]) - } else if rawValue == "🤌🏽" { - self.init(baseEmoji: .pinchedFingers, skinTones: [.medium]) - } else if rawValue == "🤌🏾" { - self.init(baseEmoji: .pinchedFingers, skinTones: [.mediumDark]) - } else if rawValue == "🤌🏿" { - self.init(baseEmoji: .pinchedFingers, skinTones: [.dark]) - } else if rawValue == "🤏" { - self.init(baseEmoji: .pinchingHand, skinTones: nil) - } else if rawValue == "🤏🏻" { - self.init(baseEmoji: .pinchingHand, skinTones: [.light]) - } else if rawValue == "🤏🏼" { - self.init(baseEmoji: .pinchingHand, skinTones: [.mediumLight]) - } else if rawValue == "🤏🏽" { - self.init(baseEmoji: .pinchingHand, skinTones: [.medium]) - } else if rawValue == "🤏🏾" { - self.init(baseEmoji: .pinchingHand, skinTones: [.mediumDark]) - } else if rawValue == "🤏🏿" { - self.init(baseEmoji: .pinchingHand, skinTones: [.dark]) - } else if rawValue == "✌️" { - self.init(baseEmoji: .v, skinTones: nil) - } else if rawValue == "✌🏻" { - self.init(baseEmoji: .v, skinTones: [.light]) - } else if rawValue == "✌🏼" { - self.init(baseEmoji: .v, skinTones: [.mediumLight]) - } else if rawValue == "✌🏽" { - self.init(baseEmoji: .v, skinTones: [.medium]) - } else if rawValue == "✌🏾" { - self.init(baseEmoji: .v, skinTones: [.mediumDark]) - } else if rawValue == "✌🏿" { - self.init(baseEmoji: .v, skinTones: [.dark]) - } else if rawValue == "🤞" { - self.init(baseEmoji: .crossedFingers, skinTones: nil) - } else if rawValue == "🤞🏻" { - self.init(baseEmoji: .crossedFingers, skinTones: [.light]) - } else if rawValue == "🤞🏼" { - self.init(baseEmoji: .crossedFingers, skinTones: [.mediumLight]) - } else if rawValue == "🤞🏽" { - self.init(baseEmoji: .crossedFingers, skinTones: [.medium]) - } else if rawValue == "🤞🏾" { - self.init(baseEmoji: .crossedFingers, skinTones: [.mediumDark]) - } else if rawValue == "🤞🏿" { - self.init(baseEmoji: .crossedFingers, skinTones: [.dark]) - } else if rawValue == "🫰" { - self.init(baseEmoji: .handWithIndexFingerAndThumbCrossed, skinTones: nil) - } else if rawValue == "🫰🏻" { - self.init(baseEmoji: .handWithIndexFingerAndThumbCrossed, skinTones: [.light]) - } else if rawValue == "🫰🏼" { - self.init(baseEmoji: .handWithIndexFingerAndThumbCrossed, skinTones: [.mediumLight]) - } else if rawValue == "🫰🏽" { - self.init(baseEmoji: .handWithIndexFingerAndThumbCrossed, skinTones: [.medium]) - } else if rawValue == "🫰🏾" { - self.init(baseEmoji: .handWithIndexFingerAndThumbCrossed, skinTones: [.mediumDark]) - } else if rawValue == "🫰🏿" { - self.init(baseEmoji: .handWithIndexFingerAndThumbCrossed, skinTones: [.dark]) - } else if rawValue == "🤟" { - self.init(baseEmoji: .iLoveYouHandSign, skinTones: nil) - } else if rawValue == "🤟🏻" { - self.init(baseEmoji: .iLoveYouHandSign, skinTones: [.light]) - } else if rawValue == "🤟🏼" { - self.init(baseEmoji: .iLoveYouHandSign, skinTones: [.mediumLight]) - } else if rawValue == "🤟🏽" { - self.init(baseEmoji: .iLoveYouHandSign, skinTones: [.medium]) - } else if rawValue == "🤟🏾" { - self.init(baseEmoji: .iLoveYouHandSign, skinTones: [.mediumDark]) - } else if rawValue == "🤟🏿" { - self.init(baseEmoji: .iLoveYouHandSign, skinTones: [.dark]) - } else if rawValue == "🤘" { - self.init(baseEmoji: .theHorns, skinTones: nil) - } else if rawValue == "🤘🏻" { - self.init(baseEmoji: .theHorns, skinTones: [.light]) - } else if rawValue == "🤘🏼" { - self.init(baseEmoji: .theHorns, skinTones: [.mediumLight]) - } else if rawValue == "🤘🏽" { - self.init(baseEmoji: .theHorns, skinTones: [.medium]) - } else if rawValue == "🤘🏾" { - self.init(baseEmoji: .theHorns, skinTones: [.mediumDark]) - } else if rawValue == "🤘🏿" { - self.init(baseEmoji: .theHorns, skinTones: [.dark]) - } else if rawValue == "🤙" { - self.init(baseEmoji: .callMeHand, skinTones: nil) - } else if rawValue == "🤙🏻" { - self.init(baseEmoji: .callMeHand, skinTones: [.light]) - } else if rawValue == "🤙🏼" { - self.init(baseEmoji: .callMeHand, skinTones: [.mediumLight]) - } else if rawValue == "🤙🏽" { - self.init(baseEmoji: .callMeHand, skinTones: [.medium]) - } else if rawValue == "🤙🏾" { - self.init(baseEmoji: .callMeHand, skinTones: [.mediumDark]) - } else if rawValue == "🤙🏿" { - self.init(baseEmoji: .callMeHand, skinTones: [.dark]) - } else if rawValue == "👈" { - self.init(baseEmoji: .pointLeft, skinTones: nil) - } else if rawValue == "👈🏻" { - self.init(baseEmoji: .pointLeft, skinTones: [.light]) - } else if rawValue == "👈🏼" { - self.init(baseEmoji: .pointLeft, skinTones: [.mediumLight]) - } else if rawValue == "👈🏽" { - self.init(baseEmoji: .pointLeft, skinTones: [.medium]) - } else if rawValue == "👈🏾" { - self.init(baseEmoji: .pointLeft, skinTones: [.mediumDark]) - } else if rawValue == "👈🏿" { - self.init(baseEmoji: .pointLeft, skinTones: [.dark]) - } else if rawValue == "👉" { - self.init(baseEmoji: .pointRight, skinTones: nil) - } else if rawValue == "👉🏻" { - self.init(baseEmoji: .pointRight, skinTones: [.light]) - } else if rawValue == "👉🏼" { - self.init(baseEmoji: .pointRight, skinTones: [.mediumLight]) - } else if rawValue == "👉🏽" { - self.init(baseEmoji: .pointRight, skinTones: [.medium]) - } else if rawValue == "👉🏾" { - self.init(baseEmoji: .pointRight, skinTones: [.mediumDark]) - } else if rawValue == "👉🏿" { - self.init(baseEmoji: .pointRight, skinTones: [.dark]) - } else if rawValue == "👆" { - self.init(baseEmoji: .pointUp2, skinTones: nil) - } else if rawValue == "👆🏻" { - self.init(baseEmoji: .pointUp2, skinTones: [.light]) - } else if rawValue == "👆🏼" { - self.init(baseEmoji: .pointUp2, skinTones: [.mediumLight]) - } else if rawValue == "👆🏽" { - self.init(baseEmoji: .pointUp2, skinTones: [.medium]) - } else if rawValue == "👆🏾" { - self.init(baseEmoji: .pointUp2, skinTones: [.mediumDark]) - } else if rawValue == "👆🏿" { - self.init(baseEmoji: .pointUp2, skinTones: [.dark]) - } else if rawValue == "🖕" { - self.init(baseEmoji: .middleFinger, skinTones: nil) - } else if rawValue == "🖕🏻" { - self.init(baseEmoji: .middleFinger, skinTones: [.light]) - } else if rawValue == "🖕🏼" { - self.init(baseEmoji: .middleFinger, skinTones: [.mediumLight]) - } else if rawValue == "🖕🏽" { - self.init(baseEmoji: .middleFinger, skinTones: [.medium]) - } else if rawValue == "🖕🏾" { - self.init(baseEmoji: .middleFinger, skinTones: [.mediumDark]) - } else if rawValue == "🖕🏿" { - self.init(baseEmoji: .middleFinger, skinTones: [.dark]) - } else if rawValue == "👇" { - self.init(baseEmoji: .pointDown, skinTones: nil) - } else if rawValue == "👇🏻" { - self.init(baseEmoji: .pointDown, skinTones: [.light]) - } else if rawValue == "👇🏼" { - self.init(baseEmoji: .pointDown, skinTones: [.mediumLight]) - } else if rawValue == "👇🏽" { - self.init(baseEmoji: .pointDown, skinTones: [.medium]) - } else if rawValue == "👇🏾" { - self.init(baseEmoji: .pointDown, skinTones: [.mediumDark]) - } else if rawValue == "👇🏿" { - self.init(baseEmoji: .pointDown, skinTones: [.dark]) - } else if rawValue == "☝️" { - self.init(baseEmoji: .pointUp, skinTones: nil) - } else if rawValue == "☝🏻" { - self.init(baseEmoji: .pointUp, skinTones: [.light]) - } else if rawValue == "☝🏼" { - self.init(baseEmoji: .pointUp, skinTones: [.mediumLight]) - } else if rawValue == "☝🏽" { - self.init(baseEmoji: .pointUp, skinTones: [.medium]) - } else if rawValue == "☝🏾" { - self.init(baseEmoji: .pointUp, skinTones: [.mediumDark]) - } else if rawValue == "☝🏿" { - self.init(baseEmoji: .pointUp, skinTones: [.dark]) - } else if rawValue == "🫵" { - self.init(baseEmoji: .indexPointingAtTheViewer, skinTones: nil) - } else if rawValue == "🫵🏻" { - self.init(baseEmoji: .indexPointingAtTheViewer, skinTones: [.light]) - } else if rawValue == "🫵🏼" { - self.init(baseEmoji: .indexPointingAtTheViewer, skinTones: [.mediumLight]) - } else if rawValue == "🫵🏽" { - self.init(baseEmoji: .indexPointingAtTheViewer, skinTones: [.medium]) - } else if rawValue == "🫵🏾" { - self.init(baseEmoji: .indexPointingAtTheViewer, skinTones: [.mediumDark]) - } else if rawValue == "🫵🏿" { - self.init(baseEmoji: .indexPointingAtTheViewer, skinTones: [.dark]) - } else if rawValue == "👍" { - self.init(baseEmoji: .plusOne, skinTones: nil) - } else if rawValue == "👍🏻" { - self.init(baseEmoji: .plusOne, skinTones: [.light]) - } else if rawValue == "👍🏼" { - self.init(baseEmoji: .plusOne, skinTones: [.mediumLight]) - } else if rawValue == "👍🏽" { - self.init(baseEmoji: .plusOne, skinTones: [.medium]) - } else if rawValue == "👍🏾" { - self.init(baseEmoji: .plusOne, skinTones: [.mediumDark]) - } else if rawValue == "👍🏿" { - self.init(baseEmoji: .plusOne, skinTones: [.dark]) - } else if rawValue == "👎" { - self.init(baseEmoji: .negativeOne, skinTones: nil) - } else if rawValue == "👎🏻" { - self.init(baseEmoji: .negativeOne, skinTones: [.light]) - } else if rawValue == "👎🏼" { - self.init(baseEmoji: .negativeOne, skinTones: [.mediumLight]) - } else if rawValue == "👎🏽" { - self.init(baseEmoji: .negativeOne, skinTones: [.medium]) - } else if rawValue == "👎🏾" { - self.init(baseEmoji: .negativeOne, skinTones: [.mediumDark]) - } else if rawValue == "👎🏿" { - self.init(baseEmoji: .negativeOne, skinTones: [.dark]) - } else if rawValue == "✊" { - self.init(baseEmoji: .fist, skinTones: nil) - } else if rawValue == "✊🏻" { - self.init(baseEmoji: .fist, skinTones: [.light]) - } else if rawValue == "✊🏼" { - self.init(baseEmoji: .fist, skinTones: [.mediumLight]) - } else if rawValue == "✊🏽" { - self.init(baseEmoji: .fist, skinTones: [.medium]) - } else if rawValue == "✊🏾" { - self.init(baseEmoji: .fist, skinTones: [.mediumDark]) - } else if rawValue == "✊🏿" { - self.init(baseEmoji: .fist, skinTones: [.dark]) - } else if rawValue == "👊" { - self.init(baseEmoji: .facepunch, skinTones: nil) - } else if rawValue == "👊🏻" { - self.init(baseEmoji: .facepunch, skinTones: [.light]) - } else if rawValue == "👊🏼" { - self.init(baseEmoji: .facepunch, skinTones: [.mediumLight]) - } else if rawValue == "👊🏽" { - self.init(baseEmoji: .facepunch, skinTones: [.medium]) - } else if rawValue == "👊🏾" { - self.init(baseEmoji: .facepunch, skinTones: [.mediumDark]) - } else if rawValue == "👊🏿" { - self.init(baseEmoji: .facepunch, skinTones: [.dark]) - } else if rawValue == "🤛" { - self.init(baseEmoji: .leftFacingFist, skinTones: nil) - } else if rawValue == "🤛🏻" { - self.init(baseEmoji: .leftFacingFist, skinTones: [.light]) - } else if rawValue == "🤛🏼" { - self.init(baseEmoji: .leftFacingFist, skinTones: [.mediumLight]) - } else if rawValue == "🤛🏽" { - self.init(baseEmoji: .leftFacingFist, skinTones: [.medium]) - } else if rawValue == "🤛🏾" { - self.init(baseEmoji: .leftFacingFist, skinTones: [.mediumDark]) - } else if rawValue == "🤛🏿" { - self.init(baseEmoji: .leftFacingFist, skinTones: [.dark]) - } else if rawValue == "🤜" { - self.init(baseEmoji: .rightFacingFist, skinTones: nil) - } else if rawValue == "🤜🏻" { - self.init(baseEmoji: .rightFacingFist, skinTones: [.light]) - } else if rawValue == "🤜🏼" { - self.init(baseEmoji: .rightFacingFist, skinTones: [.mediumLight]) - } else if rawValue == "🤜🏽" { - self.init(baseEmoji: .rightFacingFist, skinTones: [.medium]) - } else if rawValue == "🤜🏾" { - self.init(baseEmoji: .rightFacingFist, skinTones: [.mediumDark]) - } else if rawValue == "🤜🏿" { - self.init(baseEmoji: .rightFacingFist, skinTones: [.dark]) - } else if rawValue == "👏" { - self.init(baseEmoji: .clap, skinTones: nil) - } else if rawValue == "👏🏻" { - self.init(baseEmoji: .clap, skinTones: [.light]) - } else if rawValue == "👏🏼" { - self.init(baseEmoji: .clap, skinTones: [.mediumLight]) - } else if rawValue == "👏🏽" { - self.init(baseEmoji: .clap, skinTones: [.medium]) - } else if rawValue == "👏🏾" { - self.init(baseEmoji: .clap, skinTones: [.mediumDark]) - } else if rawValue == "👏🏿" { - self.init(baseEmoji: .clap, skinTones: [.dark]) - } else if rawValue == "🙌" { - self.init(baseEmoji: .raisedHands, skinTones: nil) - } else if rawValue == "🙌🏻" { - self.init(baseEmoji: .raisedHands, skinTones: [.light]) - } else if rawValue == "🙌🏼" { - self.init(baseEmoji: .raisedHands, skinTones: [.mediumLight]) - } else if rawValue == "🙌🏽" { - self.init(baseEmoji: .raisedHands, skinTones: [.medium]) - } else if rawValue == "🙌🏾" { - self.init(baseEmoji: .raisedHands, skinTones: [.mediumDark]) - } else if rawValue == "🙌🏿" { - self.init(baseEmoji: .raisedHands, skinTones: [.dark]) - } else if rawValue == "🫶" { - self.init(baseEmoji: .heartHands, skinTones: nil) - } else if rawValue == "🫶🏻" { - self.init(baseEmoji: .heartHands, skinTones: [.light]) - } else if rawValue == "🫶🏼" { - self.init(baseEmoji: .heartHands, skinTones: [.mediumLight]) - } else if rawValue == "🫶🏽" { - self.init(baseEmoji: .heartHands, skinTones: [.medium]) - } else if rawValue == "🫶🏾" { - self.init(baseEmoji: .heartHands, skinTones: [.mediumDark]) - } else if rawValue == "🫶🏿" { - self.init(baseEmoji: .heartHands, skinTones: [.dark]) - } else if rawValue == "👐" { - self.init(baseEmoji: .openHands, skinTones: nil) - } else if rawValue == "👐🏻" { - self.init(baseEmoji: .openHands, skinTones: [.light]) - } else if rawValue == "👐🏼" { - self.init(baseEmoji: .openHands, skinTones: [.mediumLight]) - } else if rawValue == "👐🏽" { - self.init(baseEmoji: .openHands, skinTones: [.medium]) - } else if rawValue == "👐🏾" { - self.init(baseEmoji: .openHands, skinTones: [.mediumDark]) - } else if rawValue == "👐🏿" { - self.init(baseEmoji: .openHands, skinTones: [.dark]) - } else if rawValue == "🤲" { - self.init(baseEmoji: .palmsUpTogether, skinTones: nil) - } else if rawValue == "🤲🏻" { - self.init(baseEmoji: .palmsUpTogether, skinTones: [.light]) - } else if rawValue == "🤲🏼" { - self.init(baseEmoji: .palmsUpTogether, skinTones: [.mediumLight]) - } else if rawValue == "🤲🏽" { - self.init(baseEmoji: .palmsUpTogether, skinTones: [.medium]) - } else if rawValue == "🤲🏾" { - self.init(baseEmoji: .palmsUpTogether, skinTones: [.mediumDark]) - } else if rawValue == "🤲🏿" { - self.init(baseEmoji: .palmsUpTogether, skinTones: [.dark]) - } else if rawValue == "🤝" { - self.init(baseEmoji: .handshake, skinTones: nil) - } else if rawValue == "🤝🏻" { - self.init(baseEmoji: .handshake, skinTones: [.light]) - } else if rawValue == "🫱🏻‍🫲🏼" { - self.init(baseEmoji: .handshake, skinTones: [.light, .mediumLight]) - } else if rawValue == "🫱🏻‍🫲🏽" { - self.init(baseEmoji: .handshake, skinTones: [.light, .medium]) - } else if rawValue == "🫱🏻‍🫲🏾" { - self.init(baseEmoji: .handshake, skinTones: [.light, .mediumDark]) - } else if rawValue == "🫱🏻‍🫲🏿" { - self.init(baseEmoji: .handshake, skinTones: [.light, .dark]) - } else if rawValue == "🤝🏼" { - self.init(baseEmoji: .handshake, skinTones: [.mediumLight]) - } else if rawValue == "🫱🏼‍🫲🏻" { - self.init(baseEmoji: .handshake, skinTones: [.mediumLight, .light]) - } else if rawValue == "🫱🏼‍🫲🏽" { - self.init(baseEmoji: .handshake, skinTones: [.mediumLight, .medium]) - } else if rawValue == "🫱🏼‍🫲🏾" { - self.init(baseEmoji: .handshake, skinTones: [.mediumLight, .mediumDark]) - } else if rawValue == "🫱🏼‍🫲🏿" { - self.init(baseEmoji: .handshake, skinTones: [.mediumLight, .dark]) - } else if rawValue == "🤝🏽" { - self.init(baseEmoji: .handshake, skinTones: [.medium]) - } else if rawValue == "🫱🏽‍🫲🏻" { - self.init(baseEmoji: .handshake, skinTones: [.medium, .light]) - } else if rawValue == "🫱🏽‍🫲🏼" { - self.init(baseEmoji: .handshake, skinTones: [.medium, .mediumLight]) - } else if rawValue == "🫱🏽‍🫲🏾" { - self.init(baseEmoji: .handshake, skinTones: [.medium, .mediumDark]) - } else if rawValue == "🫱🏽‍🫲🏿" { - self.init(baseEmoji: .handshake, skinTones: [.medium, .dark]) - } else if rawValue == "🤝🏾" { - self.init(baseEmoji: .handshake, skinTones: [.mediumDark]) - } else if rawValue == "🫱🏾‍🫲🏻" { - self.init(baseEmoji: .handshake, skinTones: [.mediumDark, .light]) - } else if rawValue == "🫱🏾‍🫲🏼" { - self.init(baseEmoji: .handshake, skinTones: [.mediumDark, .mediumLight]) - } else if rawValue == "🫱🏾‍🫲🏽" { - self.init(baseEmoji: .handshake, skinTones: [.mediumDark, .medium]) - } else if rawValue == "🫱🏾‍🫲🏿" { - self.init(baseEmoji: .handshake, skinTones: [.mediumDark, .dark]) - } else if rawValue == "🤝🏿" { - self.init(baseEmoji: .handshake, skinTones: [.dark]) - } else if rawValue == "🫱🏿‍🫲🏻" { - self.init(baseEmoji: .handshake, skinTones: [.dark, .light]) - } else if rawValue == "🫱🏿‍🫲🏼" { - self.init(baseEmoji: .handshake, skinTones: [.dark, .mediumLight]) - } else if rawValue == "🫱🏿‍🫲🏽" { - self.init(baseEmoji: .handshake, skinTones: [.dark, .medium]) - } else if rawValue == "🫱🏿‍🫲🏾" { - self.init(baseEmoji: .handshake, skinTones: [.dark, .mediumDark]) - } else if rawValue == "🙏" { - self.init(baseEmoji: .pray, skinTones: nil) - } else if rawValue == "🙏🏻" { - self.init(baseEmoji: .pray, skinTones: [.light]) - } else if rawValue == "🙏🏼" { - self.init(baseEmoji: .pray, skinTones: [.mediumLight]) - } else if rawValue == "🙏🏽" { - self.init(baseEmoji: .pray, skinTones: [.medium]) - } else if rawValue == "🙏🏾" { - self.init(baseEmoji: .pray, skinTones: [.mediumDark]) - } else if rawValue == "🙏🏿" { - self.init(baseEmoji: .pray, skinTones: [.dark]) - } else if rawValue == "✍️" { - self.init(baseEmoji: .writingHand, skinTones: nil) - } else if rawValue == "✍🏻" { - self.init(baseEmoji: .writingHand, skinTones: [.light]) - } else if rawValue == "✍🏼" { - self.init(baseEmoji: .writingHand, skinTones: [.mediumLight]) - } else if rawValue == "✍🏽" { - self.init(baseEmoji: .writingHand, skinTones: [.medium]) - } else if rawValue == "✍🏾" { - self.init(baseEmoji: .writingHand, skinTones: [.mediumDark]) - } else if rawValue == "✍🏿" { - self.init(baseEmoji: .writingHand, skinTones: [.dark]) - } else if rawValue == "💅" { - self.init(baseEmoji: .nailCare, skinTones: nil) - } else if rawValue == "💅🏻" { - self.init(baseEmoji: .nailCare, skinTones: [.light]) - } else if rawValue == "💅🏼" { - self.init(baseEmoji: .nailCare, skinTones: [.mediumLight]) - } else if rawValue == "💅🏽" { - self.init(baseEmoji: .nailCare, skinTones: [.medium]) - } else if rawValue == "💅🏾" { - self.init(baseEmoji: .nailCare, skinTones: [.mediumDark]) - } else if rawValue == "💅🏿" { - self.init(baseEmoji: .nailCare, skinTones: [.dark]) - } else if rawValue == "🤳" { - self.init(baseEmoji: .selfie, skinTones: nil) - } else if rawValue == "🤳🏻" { - self.init(baseEmoji: .selfie, skinTones: [.light]) - } else if rawValue == "🤳🏼" { - self.init(baseEmoji: .selfie, skinTones: [.mediumLight]) - } else if rawValue == "🤳🏽" { - self.init(baseEmoji: .selfie, skinTones: [.medium]) - } else if rawValue == "🤳🏾" { - self.init(baseEmoji: .selfie, skinTones: [.mediumDark]) - } else if rawValue == "🤳🏿" { - self.init(baseEmoji: .selfie, skinTones: [.dark]) - } else if rawValue == "💪" { - self.init(baseEmoji: .muscle, skinTones: nil) - } else if rawValue == "💪🏻" { - self.init(baseEmoji: .muscle, skinTones: [.light]) - } else if rawValue == "💪🏼" { - self.init(baseEmoji: .muscle, skinTones: [.mediumLight]) - } else if rawValue == "💪🏽" { - self.init(baseEmoji: .muscle, skinTones: [.medium]) - } else if rawValue == "💪🏾" { - self.init(baseEmoji: .muscle, skinTones: [.mediumDark]) - } else if rawValue == "💪🏿" { - self.init(baseEmoji: .muscle, skinTones: [.dark]) - } else if rawValue == "🦾" { - self.init(baseEmoji: .mechanicalArm, skinTones: nil) - } else if rawValue == "🦿" { - self.init(baseEmoji: .mechanicalLeg, skinTones: nil) - } else if rawValue == "🦵" { - self.init(baseEmoji: .leg, skinTones: nil) - } else if rawValue == "🦵🏻" { - self.init(baseEmoji: .leg, skinTones: [.light]) - } else if rawValue == "🦵🏼" { - self.init(baseEmoji: .leg, skinTones: [.mediumLight]) - } else if rawValue == "🦵🏽" { - self.init(baseEmoji: .leg, skinTones: [.medium]) - } else if rawValue == "🦵🏾" { - self.init(baseEmoji: .leg, skinTones: [.mediumDark]) - } else if rawValue == "🦵🏿" { - self.init(baseEmoji: .leg, skinTones: [.dark]) - } else if rawValue == "🦶" { - self.init(baseEmoji: .foot, skinTones: nil) - } else if rawValue == "🦶🏻" { - self.init(baseEmoji: .foot, skinTones: [.light]) - } else if rawValue == "🦶🏼" { - self.init(baseEmoji: .foot, skinTones: [.mediumLight]) - } else if rawValue == "🦶🏽" { - self.init(baseEmoji: .foot, skinTones: [.medium]) - } else if rawValue == "🦶🏾" { - self.init(baseEmoji: .foot, skinTones: [.mediumDark]) - } else if rawValue == "🦶🏿" { - self.init(baseEmoji: .foot, skinTones: [.dark]) - } else if rawValue == "👂" { - self.init(baseEmoji: .ear, skinTones: nil) - } else if rawValue == "👂🏻" { - self.init(baseEmoji: .ear, skinTones: [.light]) - } else if rawValue == "👂🏼" { - self.init(baseEmoji: .ear, skinTones: [.mediumLight]) - } else if rawValue == "👂🏽" { - self.init(baseEmoji: .ear, skinTones: [.medium]) - } else if rawValue == "👂🏾" { - self.init(baseEmoji: .ear, skinTones: [.mediumDark]) - } else if rawValue == "👂🏿" { - self.init(baseEmoji: .ear, skinTones: [.dark]) - } else if rawValue == "🦻" { - self.init(baseEmoji: .earWithHearingAid, skinTones: nil) - } else if rawValue == "🦻🏻" { - self.init(baseEmoji: .earWithHearingAid, skinTones: [.light]) - } else if rawValue == "🦻🏼" { - self.init(baseEmoji: .earWithHearingAid, skinTones: [.mediumLight]) - } else if rawValue == "🦻🏽" { - self.init(baseEmoji: .earWithHearingAid, skinTones: [.medium]) - } else if rawValue == "🦻🏾" { - self.init(baseEmoji: .earWithHearingAid, skinTones: [.mediumDark]) - } else if rawValue == "🦻🏿" { - self.init(baseEmoji: .earWithHearingAid, skinTones: [.dark]) - } else if rawValue == "👃" { - self.init(baseEmoji: .nose, skinTones: nil) - } else if rawValue == "👃🏻" { - self.init(baseEmoji: .nose, skinTones: [.light]) - } else if rawValue == "👃🏼" { - self.init(baseEmoji: .nose, skinTones: [.mediumLight]) - } else if rawValue == "👃🏽" { - self.init(baseEmoji: .nose, skinTones: [.medium]) - } else if rawValue == "👃🏾" { - self.init(baseEmoji: .nose, skinTones: [.mediumDark]) - } else if rawValue == "👃🏿" { - self.init(baseEmoji: .nose, skinTones: [.dark]) - } else if rawValue == "🧠" { - self.init(baseEmoji: .brain, skinTones: nil) - } else if rawValue == "🫀" { - self.init(baseEmoji: .anatomicalHeart, skinTones: nil) - } else if rawValue == "🫁" { - self.init(baseEmoji: .lungs, skinTones: nil) - } else if rawValue == "🦷" { - self.init(baseEmoji: .tooth, skinTones: nil) - } else if rawValue == "🦴" { - self.init(baseEmoji: .bone, skinTones: nil) - } else if rawValue == "👀" { - self.init(baseEmoji: .eyes, skinTones: nil) - } else if rawValue == "👁️" { - self.init(baseEmoji: .eye, skinTones: nil) - } else if rawValue == "👅" { - self.init(baseEmoji: .tongue, skinTones: nil) - } else if rawValue == "👄" { - self.init(baseEmoji: .lips, skinTones: nil) - } else if rawValue == "🫦" { - self.init(baseEmoji: .bitingLip, skinTones: nil) - } else if rawValue == "👶" { - self.init(baseEmoji: .baby, skinTones: nil) - } else if rawValue == "👶🏻" { - self.init(baseEmoji: .baby, skinTones: [.light]) - } else if rawValue == "👶🏼" { - self.init(baseEmoji: .baby, skinTones: [.mediumLight]) - } else if rawValue == "👶🏽" { - self.init(baseEmoji: .baby, skinTones: [.medium]) - } else if rawValue == "👶🏾" { - self.init(baseEmoji: .baby, skinTones: [.mediumDark]) - } else if rawValue == "👶🏿" { - self.init(baseEmoji: .baby, skinTones: [.dark]) - } else if rawValue == "🧒" { - self.init(baseEmoji: .child, skinTones: nil) - } else if rawValue == "🧒🏻" { - self.init(baseEmoji: .child, skinTones: [.light]) - } else if rawValue == "🧒🏼" { - self.init(baseEmoji: .child, skinTones: [.mediumLight]) - } else if rawValue == "🧒🏽" { - self.init(baseEmoji: .child, skinTones: [.medium]) - } else if rawValue == "🧒🏾" { - self.init(baseEmoji: .child, skinTones: [.mediumDark]) - } else if rawValue == "🧒🏿" { - self.init(baseEmoji: .child, skinTones: [.dark]) - } else if rawValue == "👦" { - self.init(baseEmoji: .boy, skinTones: nil) - } else if rawValue == "👦🏻" { - self.init(baseEmoji: .boy, skinTones: [.light]) - } else if rawValue == "👦🏼" { - self.init(baseEmoji: .boy, skinTones: [.mediumLight]) - } else if rawValue == "👦🏽" { - self.init(baseEmoji: .boy, skinTones: [.medium]) - } else if rawValue == "👦🏾" { - self.init(baseEmoji: .boy, skinTones: [.mediumDark]) - } else if rawValue == "👦🏿" { - self.init(baseEmoji: .boy, skinTones: [.dark]) - } else if rawValue == "👧" { - self.init(baseEmoji: .girl, skinTones: nil) - } else if rawValue == "👧🏻" { - self.init(baseEmoji: .girl, skinTones: [.light]) - } else if rawValue == "👧🏼" { - self.init(baseEmoji: .girl, skinTones: [.mediumLight]) - } else if rawValue == "👧🏽" { - self.init(baseEmoji: .girl, skinTones: [.medium]) - } else if rawValue == "👧🏾" { - self.init(baseEmoji: .girl, skinTones: [.mediumDark]) - } else if rawValue == "👧🏿" { - self.init(baseEmoji: .girl, skinTones: [.dark]) - } else if rawValue == "🧑" { - self.init(baseEmoji: .adult, skinTones: nil) - } else if rawValue == "🧑🏻" { - self.init(baseEmoji: .adult, skinTones: [.light]) - } else if rawValue == "🧑🏼" { - self.init(baseEmoji: .adult, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽" { - self.init(baseEmoji: .adult, skinTones: [.medium]) - } else if rawValue == "🧑🏾" { - self.init(baseEmoji: .adult, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿" { - self.init(baseEmoji: .adult, skinTones: [.dark]) - } else if rawValue == "👱" { - self.init(baseEmoji: .personWithBlondHair, skinTones: nil) - } else if rawValue == "👱🏻" { - self.init(baseEmoji: .personWithBlondHair, skinTones: [.light]) - } else if rawValue == "👱🏼" { - self.init(baseEmoji: .personWithBlondHair, skinTones: [.mediumLight]) - } else if rawValue == "👱🏽" { - self.init(baseEmoji: .personWithBlondHair, skinTones: [.medium]) - } else if rawValue == "👱🏾" { - self.init(baseEmoji: .personWithBlondHair, skinTones: [.mediumDark]) - } else if rawValue == "👱🏿" { - self.init(baseEmoji: .personWithBlondHair, skinTones: [.dark]) - } else if rawValue == "👨" { - self.init(baseEmoji: .man, skinTones: nil) - } else if rawValue == "👨🏻" { - self.init(baseEmoji: .man, skinTones: [.light]) - } else if rawValue == "👨🏼" { - self.init(baseEmoji: .man, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽" { - self.init(baseEmoji: .man, skinTones: [.medium]) - } else if rawValue == "👨🏾" { - self.init(baseEmoji: .man, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿" { - self.init(baseEmoji: .man, skinTones: [.dark]) - } else if rawValue == "🧔" { - self.init(baseEmoji: .beardedPerson, skinTones: nil) - } else if rawValue == "🧔🏻" { - self.init(baseEmoji: .beardedPerson, skinTones: [.light]) - } else if rawValue == "🧔🏼" { - self.init(baseEmoji: .beardedPerson, skinTones: [.mediumLight]) - } else if rawValue == "🧔🏽" { - self.init(baseEmoji: .beardedPerson, skinTones: [.medium]) - } else if rawValue == "🧔🏾" { - self.init(baseEmoji: .beardedPerson, skinTones: [.mediumDark]) - } else if rawValue == "🧔🏿" { - self.init(baseEmoji: .beardedPerson, skinTones: [.dark]) - } else if rawValue == "🧔‍♂️" { - self.init(baseEmoji: .manWithBeard, skinTones: nil) - } else if rawValue == "🧔🏻‍♂️" { - self.init(baseEmoji: .manWithBeard, skinTones: [.light]) - } else if rawValue == "🧔🏼‍♂️" { - self.init(baseEmoji: .manWithBeard, skinTones: [.mediumLight]) - } else if rawValue == "🧔🏽‍♂️" { - self.init(baseEmoji: .manWithBeard, skinTones: [.medium]) - } else if rawValue == "🧔🏾‍♂️" { - self.init(baseEmoji: .manWithBeard, skinTones: [.mediumDark]) - } else if rawValue == "🧔🏿‍♂️" { - self.init(baseEmoji: .manWithBeard, skinTones: [.dark]) - } else if rawValue == "🧔‍♀️" { - self.init(baseEmoji: .womanWithBeard, skinTones: nil) - } else if rawValue == "🧔🏻‍♀️" { - self.init(baseEmoji: .womanWithBeard, skinTones: [.light]) - } else if rawValue == "🧔🏼‍♀️" { - self.init(baseEmoji: .womanWithBeard, skinTones: [.mediumLight]) - } else if rawValue == "🧔🏽‍♀️" { - self.init(baseEmoji: .womanWithBeard, skinTones: [.medium]) - } else if rawValue == "🧔🏾‍♀️" { - self.init(baseEmoji: .womanWithBeard, skinTones: [.mediumDark]) - } else if rawValue == "🧔🏿‍♀️" { - self.init(baseEmoji: .womanWithBeard, skinTones: [.dark]) - } else if rawValue == "👨‍🦰" { - self.init(baseEmoji: .redHairedMan, skinTones: nil) - } else if rawValue == "👨🏻‍🦰" { - self.init(baseEmoji: .redHairedMan, skinTones: [.light]) - } else if rawValue == "👨🏼‍🦰" { - self.init(baseEmoji: .redHairedMan, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🦰" { - self.init(baseEmoji: .redHairedMan, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🦰" { - self.init(baseEmoji: .redHairedMan, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🦰" { - self.init(baseEmoji: .redHairedMan, skinTones: [.dark]) - } else if rawValue == "👨‍🦱" { - self.init(baseEmoji: .curlyHairedMan, skinTones: nil) - } else if rawValue == "👨🏻‍🦱" { - self.init(baseEmoji: .curlyHairedMan, skinTones: [.light]) - } else if rawValue == "👨🏼‍🦱" { - self.init(baseEmoji: .curlyHairedMan, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🦱" { - self.init(baseEmoji: .curlyHairedMan, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🦱" { - self.init(baseEmoji: .curlyHairedMan, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🦱" { - self.init(baseEmoji: .curlyHairedMan, skinTones: [.dark]) - } else if rawValue == "👨‍🦳" { - self.init(baseEmoji: .whiteHairedMan, skinTones: nil) - } else if rawValue == "👨🏻‍🦳" { - self.init(baseEmoji: .whiteHairedMan, skinTones: [.light]) - } else if rawValue == "👨🏼‍🦳" { - self.init(baseEmoji: .whiteHairedMan, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🦳" { - self.init(baseEmoji: .whiteHairedMan, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🦳" { - self.init(baseEmoji: .whiteHairedMan, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🦳" { - self.init(baseEmoji: .whiteHairedMan, skinTones: [.dark]) - } else if rawValue == "👨‍🦲" { - self.init(baseEmoji: .baldMan, skinTones: nil) - } else if rawValue == "👨🏻‍🦲" { - self.init(baseEmoji: .baldMan, skinTones: [.light]) - } else if rawValue == "👨🏼‍🦲" { - self.init(baseEmoji: .baldMan, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🦲" { - self.init(baseEmoji: .baldMan, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🦲" { - self.init(baseEmoji: .baldMan, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🦲" { - self.init(baseEmoji: .baldMan, skinTones: [.dark]) - } else if rawValue == "👩" { - self.init(baseEmoji: .woman, skinTones: nil) - } else if rawValue == "👩🏻" { - self.init(baseEmoji: .woman, skinTones: [.light]) - } else if rawValue == "👩🏼" { - self.init(baseEmoji: .woman, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽" { - self.init(baseEmoji: .woman, skinTones: [.medium]) - } else if rawValue == "👩🏾" { - self.init(baseEmoji: .woman, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿" { - self.init(baseEmoji: .woman, skinTones: [.dark]) - } else if rawValue == "👩‍🦰" { - self.init(baseEmoji: .redHairedWoman, skinTones: nil) - } else if rawValue == "👩🏻‍🦰" { - self.init(baseEmoji: .redHairedWoman, skinTones: [.light]) - } else if rawValue == "👩🏼‍🦰" { - self.init(baseEmoji: .redHairedWoman, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🦰" { - self.init(baseEmoji: .redHairedWoman, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🦰" { - self.init(baseEmoji: .redHairedWoman, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🦰" { - self.init(baseEmoji: .redHairedWoman, skinTones: [.dark]) - } else if rawValue == "🧑‍🦰" { - self.init(baseEmoji: .redHairedPerson, skinTones: nil) - } else if rawValue == "🧑🏻‍🦰" { - self.init(baseEmoji: .redHairedPerson, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🦰" { - self.init(baseEmoji: .redHairedPerson, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🦰" { - self.init(baseEmoji: .redHairedPerson, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🦰" { - self.init(baseEmoji: .redHairedPerson, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🦰" { - self.init(baseEmoji: .redHairedPerson, skinTones: [.dark]) - } else if rawValue == "👩‍🦱" { - self.init(baseEmoji: .curlyHairedWoman, skinTones: nil) - } else if rawValue == "👩🏻‍🦱" { - self.init(baseEmoji: .curlyHairedWoman, skinTones: [.light]) - } else if rawValue == "👩🏼‍🦱" { - self.init(baseEmoji: .curlyHairedWoman, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🦱" { - self.init(baseEmoji: .curlyHairedWoman, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🦱" { - self.init(baseEmoji: .curlyHairedWoman, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🦱" { - self.init(baseEmoji: .curlyHairedWoman, skinTones: [.dark]) - } else if rawValue == "🧑‍🦱" { - self.init(baseEmoji: .curlyHairedPerson, skinTones: nil) - } else if rawValue == "🧑🏻‍🦱" { - self.init(baseEmoji: .curlyHairedPerson, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🦱" { - self.init(baseEmoji: .curlyHairedPerson, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🦱" { - self.init(baseEmoji: .curlyHairedPerson, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🦱" { - self.init(baseEmoji: .curlyHairedPerson, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🦱" { - self.init(baseEmoji: .curlyHairedPerson, skinTones: [.dark]) - } else if rawValue == "👩‍🦳" { - self.init(baseEmoji: .whiteHairedWoman, skinTones: nil) - } else if rawValue == "👩🏻‍🦳" { - self.init(baseEmoji: .whiteHairedWoman, skinTones: [.light]) - } else if rawValue == "👩🏼‍🦳" { - self.init(baseEmoji: .whiteHairedWoman, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🦳" { - self.init(baseEmoji: .whiteHairedWoman, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🦳" { - self.init(baseEmoji: .whiteHairedWoman, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🦳" { - self.init(baseEmoji: .whiteHairedWoman, skinTones: [.dark]) - } else if rawValue == "🧑‍🦳" { - self.init(baseEmoji: .whiteHairedPerson, skinTones: nil) - } else if rawValue == "🧑🏻‍🦳" { - self.init(baseEmoji: .whiteHairedPerson, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🦳" { - self.init(baseEmoji: .whiteHairedPerson, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🦳" { - self.init(baseEmoji: .whiteHairedPerson, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🦳" { - self.init(baseEmoji: .whiteHairedPerson, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🦳" { - self.init(baseEmoji: .whiteHairedPerson, skinTones: [.dark]) - } else if rawValue == "👩‍🦲" { - self.init(baseEmoji: .baldWoman, skinTones: nil) - } else if rawValue == "👩🏻‍🦲" { - self.init(baseEmoji: .baldWoman, skinTones: [.light]) - } else if rawValue == "👩🏼‍🦲" { - self.init(baseEmoji: .baldWoman, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🦲" { - self.init(baseEmoji: .baldWoman, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🦲" { - self.init(baseEmoji: .baldWoman, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🦲" { - self.init(baseEmoji: .baldWoman, skinTones: [.dark]) - } else if rawValue == "🧑‍🦲" { - self.init(baseEmoji: .baldPerson, skinTones: nil) - } else if rawValue == "🧑🏻‍🦲" { - self.init(baseEmoji: .baldPerson, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🦲" { - self.init(baseEmoji: .baldPerson, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🦲" { - self.init(baseEmoji: .baldPerson, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🦲" { - self.init(baseEmoji: .baldPerson, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🦲" { - self.init(baseEmoji: .baldPerson, skinTones: [.dark]) - } else if rawValue == "👱‍♀️" { - self.init(baseEmoji: .blondHairedWoman, skinTones: nil) - } else if rawValue == "👱🏻‍♀️" { - self.init(baseEmoji: .blondHairedWoman, skinTones: [.light]) - } else if rawValue == "👱🏼‍♀️" { - self.init(baseEmoji: .blondHairedWoman, skinTones: [.mediumLight]) - } else if rawValue == "👱🏽‍♀️" { - self.init(baseEmoji: .blondHairedWoman, skinTones: [.medium]) - } else if rawValue == "👱🏾‍♀️" { - self.init(baseEmoji: .blondHairedWoman, skinTones: [.mediumDark]) - } else if rawValue == "👱🏿‍♀️" { - self.init(baseEmoji: .blondHairedWoman, skinTones: [.dark]) - } else if rawValue == "👱‍♂️" { - self.init(baseEmoji: .blondHairedMan, skinTones: nil) - } else if rawValue == "👱🏻‍♂️" { - self.init(baseEmoji: .blondHairedMan, skinTones: [.light]) - } else if rawValue == "👱🏼‍♂️" { - self.init(baseEmoji: .blondHairedMan, skinTones: [.mediumLight]) - } else if rawValue == "👱🏽‍♂️" { - self.init(baseEmoji: .blondHairedMan, skinTones: [.medium]) - } else if rawValue == "👱🏾‍♂️" { - self.init(baseEmoji: .blondHairedMan, skinTones: [.mediumDark]) - } else if rawValue == "👱🏿‍♂️" { - self.init(baseEmoji: .blondHairedMan, skinTones: [.dark]) - } else if rawValue == "🧓" { - self.init(baseEmoji: .olderAdult, skinTones: nil) - } else if rawValue == "🧓🏻" { - self.init(baseEmoji: .olderAdult, skinTones: [.light]) - } else if rawValue == "🧓🏼" { - self.init(baseEmoji: .olderAdult, skinTones: [.mediumLight]) - } else if rawValue == "🧓🏽" { - self.init(baseEmoji: .olderAdult, skinTones: [.medium]) - } else if rawValue == "🧓🏾" { - self.init(baseEmoji: .olderAdult, skinTones: [.mediumDark]) - } else if rawValue == "🧓🏿" { - self.init(baseEmoji: .olderAdult, skinTones: [.dark]) - } else if rawValue == "👴" { - self.init(baseEmoji: .olderMan, skinTones: nil) - } else if rawValue == "👴🏻" { - self.init(baseEmoji: .olderMan, skinTones: [.light]) - } else if rawValue == "👴🏼" { - self.init(baseEmoji: .olderMan, skinTones: [.mediumLight]) - } else if rawValue == "👴🏽" { - self.init(baseEmoji: .olderMan, skinTones: [.medium]) - } else if rawValue == "👴🏾" { - self.init(baseEmoji: .olderMan, skinTones: [.mediumDark]) - } else if rawValue == "👴🏿" { - self.init(baseEmoji: .olderMan, skinTones: [.dark]) - } else if rawValue == "👵" { - self.init(baseEmoji: .olderWoman, skinTones: nil) - } else if rawValue == "👵🏻" { - self.init(baseEmoji: .olderWoman, skinTones: [.light]) - } else if rawValue == "👵🏼" { - self.init(baseEmoji: .olderWoman, skinTones: [.mediumLight]) - } else if rawValue == "👵🏽" { - self.init(baseEmoji: .olderWoman, skinTones: [.medium]) - } else if rawValue == "👵🏾" { - self.init(baseEmoji: .olderWoman, skinTones: [.mediumDark]) - } else if rawValue == "👵🏿" { - self.init(baseEmoji: .olderWoman, skinTones: [.dark]) - } else if rawValue == "🙍" { - self.init(baseEmoji: .personFrowning, skinTones: nil) - } else if rawValue == "🙍🏻" { - self.init(baseEmoji: .personFrowning, skinTones: [.light]) - } else if rawValue == "🙍🏼" { - self.init(baseEmoji: .personFrowning, skinTones: [.mediumLight]) - } else if rawValue == "🙍🏽" { - self.init(baseEmoji: .personFrowning, skinTones: [.medium]) - } else if rawValue == "🙍🏾" { - self.init(baseEmoji: .personFrowning, skinTones: [.mediumDark]) - } else if rawValue == "🙍🏿" { - self.init(baseEmoji: .personFrowning, skinTones: [.dark]) - } else if rawValue == "🙍‍♂️" { - self.init(baseEmoji: .manFrowning, skinTones: nil) - } else if rawValue == "🙍🏻‍♂️" { - self.init(baseEmoji: .manFrowning, skinTones: [.light]) - } else if rawValue == "🙍🏼‍♂️" { - self.init(baseEmoji: .manFrowning, skinTones: [.mediumLight]) - } else if rawValue == "🙍🏽‍♂️" { - self.init(baseEmoji: .manFrowning, skinTones: [.medium]) - } else if rawValue == "🙍🏾‍♂️" { - self.init(baseEmoji: .manFrowning, skinTones: [.mediumDark]) - } else if rawValue == "🙍🏿‍♂️" { - self.init(baseEmoji: .manFrowning, skinTones: [.dark]) - } else if rawValue == "🙍‍♀️" { - self.init(baseEmoji: .womanFrowning, skinTones: nil) - } else if rawValue == "🙍🏻‍♀️" { - self.init(baseEmoji: .womanFrowning, skinTones: [.light]) - } else if rawValue == "🙍🏼‍♀️" { - self.init(baseEmoji: .womanFrowning, skinTones: [.mediumLight]) - } else if rawValue == "🙍🏽‍♀️" { - self.init(baseEmoji: .womanFrowning, skinTones: [.medium]) - } else if rawValue == "🙍🏾‍♀️" { - self.init(baseEmoji: .womanFrowning, skinTones: [.mediumDark]) - } else if rawValue == "🙍🏿‍♀️" { - self.init(baseEmoji: .womanFrowning, skinTones: [.dark]) - } else if rawValue == "🙎" { - self.init(baseEmoji: .personWithPoutingFace, skinTones: nil) - } else if rawValue == "🙎🏻" { - self.init(baseEmoji: .personWithPoutingFace, skinTones: [.light]) - } else if rawValue == "🙎🏼" { - self.init(baseEmoji: .personWithPoutingFace, skinTones: [.mediumLight]) - } else if rawValue == "🙎🏽" { - self.init(baseEmoji: .personWithPoutingFace, skinTones: [.medium]) - } else if rawValue == "🙎🏾" { - self.init(baseEmoji: .personWithPoutingFace, skinTones: [.mediumDark]) - } else if rawValue == "🙎🏿" { - self.init(baseEmoji: .personWithPoutingFace, skinTones: [.dark]) - } else if rawValue == "🙎‍♂️" { - self.init(baseEmoji: .manPouting, skinTones: nil) - } else if rawValue == "🙎🏻‍♂️" { - self.init(baseEmoji: .manPouting, skinTones: [.light]) - } else if rawValue == "🙎🏼‍♂️" { - self.init(baseEmoji: .manPouting, skinTones: [.mediumLight]) - } else if rawValue == "🙎🏽‍♂️" { - self.init(baseEmoji: .manPouting, skinTones: [.medium]) - } else if rawValue == "🙎🏾‍♂️" { - self.init(baseEmoji: .manPouting, skinTones: [.mediumDark]) - } else if rawValue == "🙎🏿‍♂️" { - self.init(baseEmoji: .manPouting, skinTones: [.dark]) - } else if rawValue == "🙎‍♀️" { - self.init(baseEmoji: .womanPouting, skinTones: nil) - } else if rawValue == "🙎🏻‍♀️" { - self.init(baseEmoji: .womanPouting, skinTones: [.light]) - } else if rawValue == "🙎🏼‍♀️" { - self.init(baseEmoji: .womanPouting, skinTones: [.mediumLight]) - } else if rawValue == "🙎🏽‍♀️" { - self.init(baseEmoji: .womanPouting, skinTones: [.medium]) - } else if rawValue == "🙎🏾‍♀️" { - self.init(baseEmoji: .womanPouting, skinTones: [.mediumDark]) - } else if rawValue == "🙎🏿‍♀️" { - self.init(baseEmoji: .womanPouting, skinTones: [.dark]) - } else if rawValue == "🙅" { - self.init(baseEmoji: .noGood, skinTones: nil) - } else if rawValue == "🙅🏻" { - self.init(baseEmoji: .noGood, skinTones: [.light]) - } else if rawValue == "🙅🏼" { - self.init(baseEmoji: .noGood, skinTones: [.mediumLight]) - } else if rawValue == "🙅🏽" { - self.init(baseEmoji: .noGood, skinTones: [.medium]) - } else if rawValue == "🙅🏾" { - self.init(baseEmoji: .noGood, skinTones: [.mediumDark]) - } else if rawValue == "🙅🏿" { - self.init(baseEmoji: .noGood, skinTones: [.dark]) - } else if rawValue == "🙅‍♂️" { - self.init(baseEmoji: .manGesturingNo, skinTones: nil) - } else if rawValue == "🙅🏻‍♂️" { - self.init(baseEmoji: .manGesturingNo, skinTones: [.light]) - } else if rawValue == "🙅🏼‍♂️" { - self.init(baseEmoji: .manGesturingNo, skinTones: [.mediumLight]) - } else if rawValue == "🙅🏽‍♂️" { - self.init(baseEmoji: .manGesturingNo, skinTones: [.medium]) - } else if rawValue == "🙅🏾‍♂️" { - self.init(baseEmoji: .manGesturingNo, skinTones: [.mediumDark]) - } else if rawValue == "🙅🏿‍♂️" { - self.init(baseEmoji: .manGesturingNo, skinTones: [.dark]) - } else if rawValue == "🙅‍♀️" { - self.init(baseEmoji: .womanGesturingNo, skinTones: nil) - } else if rawValue == "🙅🏻‍♀️" { - self.init(baseEmoji: .womanGesturingNo, skinTones: [.light]) - } else if rawValue == "🙅🏼‍♀️" { - self.init(baseEmoji: .womanGesturingNo, skinTones: [.mediumLight]) - } else if rawValue == "🙅🏽‍♀️" { - self.init(baseEmoji: .womanGesturingNo, skinTones: [.medium]) - } else if rawValue == "🙅🏾‍♀️" { - self.init(baseEmoji: .womanGesturingNo, skinTones: [.mediumDark]) - } else if rawValue == "🙅🏿‍♀️" { - self.init(baseEmoji: .womanGesturingNo, skinTones: [.dark]) - } else if rawValue == "🙆" { - self.init(baseEmoji: .okWoman, skinTones: nil) - } else if rawValue == "🙆🏻" { - self.init(baseEmoji: .okWoman, skinTones: [.light]) - } else if rawValue == "🙆🏼" { - self.init(baseEmoji: .okWoman, skinTones: [.mediumLight]) - } else if rawValue == "🙆🏽" { - self.init(baseEmoji: .okWoman, skinTones: [.medium]) - } else if rawValue == "🙆🏾" { - self.init(baseEmoji: .okWoman, skinTones: [.mediumDark]) - } else if rawValue == "🙆🏿" { - self.init(baseEmoji: .okWoman, skinTones: [.dark]) - } else if rawValue == "🙆‍♂️" { - self.init(baseEmoji: .manGesturingOk, skinTones: nil) - } else if rawValue == "🙆🏻‍♂️" { - self.init(baseEmoji: .manGesturingOk, skinTones: [.light]) - } else if rawValue == "🙆🏼‍♂️" { - self.init(baseEmoji: .manGesturingOk, skinTones: [.mediumLight]) - } else if rawValue == "🙆🏽‍♂️" { - self.init(baseEmoji: .manGesturingOk, skinTones: [.medium]) - } else if rawValue == "🙆🏾‍♂️" { - self.init(baseEmoji: .manGesturingOk, skinTones: [.mediumDark]) - } else if rawValue == "🙆🏿‍♂️" { - self.init(baseEmoji: .manGesturingOk, skinTones: [.dark]) - } else if rawValue == "🙆‍♀️" { - self.init(baseEmoji: .womanGesturingOk, skinTones: nil) - } else if rawValue == "🙆🏻‍♀️" { - self.init(baseEmoji: .womanGesturingOk, skinTones: [.light]) - } else if rawValue == "🙆🏼‍♀️" { - self.init(baseEmoji: .womanGesturingOk, skinTones: [.mediumLight]) - } else if rawValue == "🙆🏽‍♀️" { - self.init(baseEmoji: .womanGesturingOk, skinTones: [.medium]) - } else if rawValue == "🙆🏾‍♀️" { - self.init(baseEmoji: .womanGesturingOk, skinTones: [.mediumDark]) - } else if rawValue == "🙆🏿‍♀️" { - self.init(baseEmoji: .womanGesturingOk, skinTones: [.dark]) - } else if rawValue == "💁" { - self.init(baseEmoji: .informationDeskPerson, skinTones: nil) - } else if rawValue == "💁🏻" { - self.init(baseEmoji: .informationDeskPerson, skinTones: [.light]) - } else if rawValue == "💁🏼" { - self.init(baseEmoji: .informationDeskPerson, skinTones: [.mediumLight]) - } else if rawValue == "💁🏽" { - self.init(baseEmoji: .informationDeskPerson, skinTones: [.medium]) - } else if rawValue == "💁🏾" { - self.init(baseEmoji: .informationDeskPerson, skinTones: [.mediumDark]) - } else if rawValue == "💁🏿" { - self.init(baseEmoji: .informationDeskPerson, skinTones: [.dark]) - } else if rawValue == "💁‍♂️" { - self.init(baseEmoji: .manTippingHand, skinTones: nil) - } else if rawValue == "💁🏻‍♂️" { - self.init(baseEmoji: .manTippingHand, skinTones: [.light]) - } else if rawValue == "💁🏼‍♂️" { - self.init(baseEmoji: .manTippingHand, skinTones: [.mediumLight]) - } else if rawValue == "💁🏽‍♂️" { - self.init(baseEmoji: .manTippingHand, skinTones: [.medium]) - } else if rawValue == "💁🏾‍♂️" { - self.init(baseEmoji: .manTippingHand, skinTones: [.mediumDark]) - } else if rawValue == "💁🏿‍♂️" { - self.init(baseEmoji: .manTippingHand, skinTones: [.dark]) - } else if rawValue == "💁‍♀️" { - self.init(baseEmoji: .womanTippingHand, skinTones: nil) - } else if rawValue == "💁🏻‍♀️" { - self.init(baseEmoji: .womanTippingHand, skinTones: [.light]) - } else if rawValue == "💁🏼‍♀️" { - self.init(baseEmoji: .womanTippingHand, skinTones: [.mediumLight]) - } else if rawValue == "💁🏽‍♀️" { - self.init(baseEmoji: .womanTippingHand, skinTones: [.medium]) - } else if rawValue == "💁🏾‍♀️" { - self.init(baseEmoji: .womanTippingHand, skinTones: [.mediumDark]) - } else if rawValue == "💁🏿‍♀️" { - self.init(baseEmoji: .womanTippingHand, skinTones: [.dark]) - } else if rawValue == "🙋" { - self.init(baseEmoji: .raisingHand, skinTones: nil) - } else if rawValue == "🙋🏻" { - self.init(baseEmoji: .raisingHand, skinTones: [.light]) - } else if rawValue == "🙋🏼" { - self.init(baseEmoji: .raisingHand, skinTones: [.mediumLight]) - } else if rawValue == "🙋🏽" { - self.init(baseEmoji: .raisingHand, skinTones: [.medium]) - } else if rawValue == "🙋🏾" { - self.init(baseEmoji: .raisingHand, skinTones: [.mediumDark]) - } else if rawValue == "🙋🏿" { - self.init(baseEmoji: .raisingHand, skinTones: [.dark]) - } else if rawValue == "🙋‍♂️" { - self.init(baseEmoji: .manRaisingHand, skinTones: nil) - } else if rawValue == "🙋🏻‍♂️" { - self.init(baseEmoji: .manRaisingHand, skinTones: [.light]) - } else if rawValue == "🙋🏼‍♂️" { - self.init(baseEmoji: .manRaisingHand, skinTones: [.mediumLight]) - } else if rawValue == "🙋🏽‍♂️" { - self.init(baseEmoji: .manRaisingHand, skinTones: [.medium]) - } else if rawValue == "🙋🏾‍♂️" { - self.init(baseEmoji: .manRaisingHand, skinTones: [.mediumDark]) - } else if rawValue == "🙋🏿‍♂️" { - self.init(baseEmoji: .manRaisingHand, skinTones: [.dark]) - } else if rawValue == "🙋‍♀️" { - self.init(baseEmoji: .womanRaisingHand, skinTones: nil) - } else if rawValue == "🙋🏻‍♀️" { - self.init(baseEmoji: .womanRaisingHand, skinTones: [.light]) - } else if rawValue == "🙋🏼‍♀️" { - self.init(baseEmoji: .womanRaisingHand, skinTones: [.mediumLight]) - } else if rawValue == "🙋🏽‍♀️" { - self.init(baseEmoji: .womanRaisingHand, skinTones: [.medium]) - } else if rawValue == "🙋🏾‍♀️" { - self.init(baseEmoji: .womanRaisingHand, skinTones: [.mediumDark]) - } else if rawValue == "🙋🏿‍♀️" { - self.init(baseEmoji: .womanRaisingHand, skinTones: [.dark]) - } else if rawValue == "🧏" { - self.init(baseEmoji: .deafPerson, skinTones: nil) - } else if rawValue == "🧏🏻" { - self.init(baseEmoji: .deafPerson, skinTones: [.light]) - } else if rawValue == "🧏🏼" { - self.init(baseEmoji: .deafPerson, skinTones: [.mediumLight]) - } else if rawValue == "🧏🏽" { - self.init(baseEmoji: .deafPerson, skinTones: [.medium]) - } else if rawValue == "🧏🏾" { - self.init(baseEmoji: .deafPerson, skinTones: [.mediumDark]) - } else if rawValue == "🧏🏿" { - self.init(baseEmoji: .deafPerson, skinTones: [.dark]) - } else if rawValue == "🧏‍♂️" { - self.init(baseEmoji: .deafMan, skinTones: nil) - } else if rawValue == "🧏🏻‍♂️" { - self.init(baseEmoji: .deafMan, skinTones: [.light]) - } else if rawValue == "🧏🏼‍♂️" { - self.init(baseEmoji: .deafMan, skinTones: [.mediumLight]) - } else if rawValue == "🧏🏽‍♂️" { - self.init(baseEmoji: .deafMan, skinTones: [.medium]) - } else if rawValue == "🧏🏾‍♂️" { - self.init(baseEmoji: .deafMan, skinTones: [.mediumDark]) - } else if rawValue == "🧏🏿‍♂️" { - self.init(baseEmoji: .deafMan, skinTones: [.dark]) - } else if rawValue == "🧏‍♀️" { - self.init(baseEmoji: .deafWoman, skinTones: nil) - } else if rawValue == "🧏🏻‍♀️" { - self.init(baseEmoji: .deafWoman, skinTones: [.light]) - } else if rawValue == "🧏🏼‍♀️" { - self.init(baseEmoji: .deafWoman, skinTones: [.mediumLight]) - } else if rawValue == "🧏🏽‍♀️" { - self.init(baseEmoji: .deafWoman, skinTones: [.medium]) - } else if rawValue == "🧏🏾‍♀️" { - self.init(baseEmoji: .deafWoman, skinTones: [.mediumDark]) - } else if rawValue == "🧏🏿‍♀️" { - self.init(baseEmoji: .deafWoman, skinTones: [.dark]) - } else if rawValue == "🙇" { - self.init(baseEmoji: .bow, skinTones: nil) - } else if rawValue == "🙇🏻" { - self.init(baseEmoji: .bow, skinTones: [.light]) - } else if rawValue == "🙇🏼" { - self.init(baseEmoji: .bow, skinTones: [.mediumLight]) - } else if rawValue == "🙇🏽" { - self.init(baseEmoji: .bow, skinTones: [.medium]) - } else if rawValue == "🙇🏾" { - self.init(baseEmoji: .bow, skinTones: [.mediumDark]) - } else if rawValue == "🙇🏿" { - self.init(baseEmoji: .bow, skinTones: [.dark]) - } else if rawValue == "🙇‍♂️" { - self.init(baseEmoji: .manBowing, skinTones: nil) - } else if rawValue == "🙇🏻‍♂️" { - self.init(baseEmoji: .manBowing, skinTones: [.light]) - } else if rawValue == "🙇🏼‍♂️" { - self.init(baseEmoji: .manBowing, skinTones: [.mediumLight]) - } else if rawValue == "🙇🏽‍♂️" { - self.init(baseEmoji: .manBowing, skinTones: [.medium]) - } else if rawValue == "🙇🏾‍♂️" { - self.init(baseEmoji: .manBowing, skinTones: [.mediumDark]) - } else if rawValue == "🙇🏿‍♂️" { - self.init(baseEmoji: .manBowing, skinTones: [.dark]) - } else if rawValue == "🙇‍♀️" { - self.init(baseEmoji: .womanBowing, skinTones: nil) - } else if rawValue == "🙇🏻‍♀️" { - self.init(baseEmoji: .womanBowing, skinTones: [.light]) - } else if rawValue == "🙇🏼‍♀️" { - self.init(baseEmoji: .womanBowing, skinTones: [.mediumLight]) - } else if rawValue == "🙇🏽‍♀️" { - self.init(baseEmoji: .womanBowing, skinTones: [.medium]) - } else if rawValue == "🙇🏾‍♀️" { - self.init(baseEmoji: .womanBowing, skinTones: [.mediumDark]) - } else if rawValue == "🙇🏿‍♀️" { - self.init(baseEmoji: .womanBowing, skinTones: [.dark]) - } else if rawValue == "🤦" { - self.init(baseEmoji: .facePalm, skinTones: nil) - } else if rawValue == "🤦🏻" { - self.init(baseEmoji: .facePalm, skinTones: [.light]) - } else if rawValue == "🤦🏼" { - self.init(baseEmoji: .facePalm, skinTones: [.mediumLight]) - } else if rawValue == "🤦🏽" { - self.init(baseEmoji: .facePalm, skinTones: [.medium]) - } else if rawValue == "🤦🏾" { - self.init(baseEmoji: .facePalm, skinTones: [.mediumDark]) - } else if rawValue == "🤦🏿" { - self.init(baseEmoji: .facePalm, skinTones: [.dark]) - } else if rawValue == "🤦‍♂️" { - self.init(baseEmoji: .manFacepalming, skinTones: nil) - } else if rawValue == "🤦🏻‍♂️" { - self.init(baseEmoji: .manFacepalming, skinTones: [.light]) - } else if rawValue == "🤦🏼‍♂️" { - self.init(baseEmoji: .manFacepalming, skinTones: [.mediumLight]) - } else if rawValue == "🤦🏽‍♂️" { - self.init(baseEmoji: .manFacepalming, skinTones: [.medium]) - } else if rawValue == "🤦🏾‍♂️" { - self.init(baseEmoji: .manFacepalming, skinTones: [.mediumDark]) - } else if rawValue == "🤦🏿‍♂️" { - self.init(baseEmoji: .manFacepalming, skinTones: [.dark]) - } else if rawValue == "🤦‍♀️" { - self.init(baseEmoji: .womanFacepalming, skinTones: nil) - } else if rawValue == "🤦🏻‍♀️" { - self.init(baseEmoji: .womanFacepalming, skinTones: [.light]) - } else if rawValue == "🤦🏼‍♀️" { - self.init(baseEmoji: .womanFacepalming, skinTones: [.mediumLight]) - } else if rawValue == "🤦🏽‍♀️" { - self.init(baseEmoji: .womanFacepalming, skinTones: [.medium]) - } else if rawValue == "🤦🏾‍♀️" { - self.init(baseEmoji: .womanFacepalming, skinTones: [.mediumDark]) - } else if rawValue == "🤦🏿‍♀️" { - self.init(baseEmoji: .womanFacepalming, skinTones: [.dark]) - } else if rawValue == "🤷" { - self.init(baseEmoji: .shrug, skinTones: nil) - } else if rawValue == "🤷🏻" { - self.init(baseEmoji: .shrug, skinTones: [.light]) - } else if rawValue == "🤷🏼" { - self.init(baseEmoji: .shrug, skinTones: [.mediumLight]) - } else if rawValue == "🤷🏽" { - self.init(baseEmoji: .shrug, skinTones: [.medium]) - } else if rawValue == "🤷🏾" { - self.init(baseEmoji: .shrug, skinTones: [.mediumDark]) - } else if rawValue == "🤷🏿" { - self.init(baseEmoji: .shrug, skinTones: [.dark]) - } else if rawValue == "🤷‍♂️" { - self.init(baseEmoji: .manShrugging, skinTones: nil) - } else if rawValue == "🤷🏻‍♂️" { - self.init(baseEmoji: .manShrugging, skinTones: [.light]) - } else if rawValue == "🤷🏼‍♂️" { - self.init(baseEmoji: .manShrugging, skinTones: [.mediumLight]) - } else if rawValue == "🤷🏽‍♂️" { - self.init(baseEmoji: .manShrugging, skinTones: [.medium]) - } else if rawValue == "🤷🏾‍♂️" { - self.init(baseEmoji: .manShrugging, skinTones: [.mediumDark]) - } else if rawValue == "🤷🏿‍♂️" { - self.init(baseEmoji: .manShrugging, skinTones: [.dark]) - } else if rawValue == "🤷‍♀️" { - self.init(baseEmoji: .womanShrugging, skinTones: nil) - } else if rawValue == "🤷🏻‍♀️" { - self.init(baseEmoji: .womanShrugging, skinTones: [.light]) - } else if rawValue == "🤷🏼‍♀️" { - self.init(baseEmoji: .womanShrugging, skinTones: [.mediumLight]) - } else if rawValue == "🤷🏽‍♀️" { - self.init(baseEmoji: .womanShrugging, skinTones: [.medium]) - } else if rawValue == "🤷🏾‍♀️" { - self.init(baseEmoji: .womanShrugging, skinTones: [.mediumDark]) - } else if rawValue == "🤷🏿‍♀️" { - self.init(baseEmoji: .womanShrugging, skinTones: [.dark]) - } else if rawValue == "🧑‍⚕️" { - self.init(baseEmoji: .healthWorker, skinTones: nil) - } else if rawValue == "🧑🏻‍⚕️" { - self.init(baseEmoji: .healthWorker, skinTones: [.light]) - } else if rawValue == "🧑🏼‍⚕️" { - self.init(baseEmoji: .healthWorker, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍⚕️" { - self.init(baseEmoji: .healthWorker, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍⚕️" { - self.init(baseEmoji: .healthWorker, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍⚕️" { - self.init(baseEmoji: .healthWorker, skinTones: [.dark]) - } else if rawValue == "👨‍⚕️" { - self.init(baseEmoji: .maleDoctor, skinTones: nil) - } else if rawValue == "👨🏻‍⚕️" { - self.init(baseEmoji: .maleDoctor, skinTones: [.light]) - } else if rawValue == "👨🏼‍⚕️" { - self.init(baseEmoji: .maleDoctor, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍⚕️" { - self.init(baseEmoji: .maleDoctor, skinTones: [.medium]) - } else if rawValue == "👨🏾‍⚕️" { - self.init(baseEmoji: .maleDoctor, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍⚕️" { - self.init(baseEmoji: .maleDoctor, skinTones: [.dark]) - } else if rawValue == "👩‍⚕️" { - self.init(baseEmoji: .femaleDoctor, skinTones: nil) - } else if rawValue == "👩🏻‍⚕️" { - self.init(baseEmoji: .femaleDoctor, skinTones: [.light]) - } else if rawValue == "👩🏼‍⚕️" { - self.init(baseEmoji: .femaleDoctor, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍⚕️" { - self.init(baseEmoji: .femaleDoctor, skinTones: [.medium]) - } else if rawValue == "👩🏾‍⚕️" { - self.init(baseEmoji: .femaleDoctor, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍⚕️" { - self.init(baseEmoji: .femaleDoctor, skinTones: [.dark]) - } else if rawValue == "🧑‍🎓" { - self.init(baseEmoji: .student, skinTones: nil) - } else if rawValue == "🧑🏻‍🎓" { - self.init(baseEmoji: .student, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🎓" { - self.init(baseEmoji: .student, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🎓" { - self.init(baseEmoji: .student, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🎓" { - self.init(baseEmoji: .student, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🎓" { - self.init(baseEmoji: .student, skinTones: [.dark]) - } else if rawValue == "👨‍🎓" { - self.init(baseEmoji: .maleStudent, skinTones: nil) - } else if rawValue == "👨🏻‍🎓" { - self.init(baseEmoji: .maleStudent, skinTones: [.light]) - } else if rawValue == "👨🏼‍🎓" { - self.init(baseEmoji: .maleStudent, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🎓" { - self.init(baseEmoji: .maleStudent, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🎓" { - self.init(baseEmoji: .maleStudent, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🎓" { - self.init(baseEmoji: .maleStudent, skinTones: [.dark]) - } else if rawValue == "👩‍🎓" { - self.init(baseEmoji: .femaleStudent, skinTones: nil) - } else if rawValue == "👩🏻‍🎓" { - self.init(baseEmoji: .femaleStudent, skinTones: [.light]) - } else if rawValue == "👩🏼‍🎓" { - self.init(baseEmoji: .femaleStudent, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🎓" { - self.init(baseEmoji: .femaleStudent, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🎓" { - self.init(baseEmoji: .femaleStudent, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🎓" { - self.init(baseEmoji: .femaleStudent, skinTones: [.dark]) - } else if rawValue == "🧑‍🏫" { - self.init(baseEmoji: .teacher, skinTones: nil) - } else if rawValue == "🧑🏻‍🏫" { - self.init(baseEmoji: .teacher, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🏫" { - self.init(baseEmoji: .teacher, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🏫" { - self.init(baseEmoji: .teacher, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🏫" { - self.init(baseEmoji: .teacher, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🏫" { - self.init(baseEmoji: .teacher, skinTones: [.dark]) - } else if rawValue == "👨‍🏫" { - self.init(baseEmoji: .maleTeacher, skinTones: nil) - } else if rawValue == "👨🏻‍🏫" { - self.init(baseEmoji: .maleTeacher, skinTones: [.light]) - } else if rawValue == "👨🏼‍🏫" { - self.init(baseEmoji: .maleTeacher, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🏫" { - self.init(baseEmoji: .maleTeacher, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🏫" { - self.init(baseEmoji: .maleTeacher, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🏫" { - self.init(baseEmoji: .maleTeacher, skinTones: [.dark]) - } else if rawValue == "👩‍🏫" { - self.init(baseEmoji: .femaleTeacher, skinTones: nil) - } else if rawValue == "👩🏻‍🏫" { - self.init(baseEmoji: .femaleTeacher, skinTones: [.light]) - } else if rawValue == "👩🏼‍🏫" { - self.init(baseEmoji: .femaleTeacher, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🏫" { - self.init(baseEmoji: .femaleTeacher, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🏫" { - self.init(baseEmoji: .femaleTeacher, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🏫" { - self.init(baseEmoji: .femaleTeacher, skinTones: [.dark]) - } else if rawValue == "🧑‍⚖️" { - self.init(baseEmoji: .judge, skinTones: nil) - } else if rawValue == "🧑🏻‍⚖️" { - self.init(baseEmoji: .judge, skinTones: [.light]) - } else if rawValue == "🧑🏼‍⚖️" { - self.init(baseEmoji: .judge, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍⚖️" { - self.init(baseEmoji: .judge, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍⚖️" { - self.init(baseEmoji: .judge, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍⚖️" { - self.init(baseEmoji: .judge, skinTones: [.dark]) - } else if rawValue == "👨‍⚖️" { - self.init(baseEmoji: .maleJudge, skinTones: nil) - } else if rawValue == "👨🏻‍⚖️" { - self.init(baseEmoji: .maleJudge, skinTones: [.light]) - } else if rawValue == "👨🏼‍⚖️" { - self.init(baseEmoji: .maleJudge, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍⚖️" { - self.init(baseEmoji: .maleJudge, skinTones: [.medium]) - } else if rawValue == "👨🏾‍⚖️" { - self.init(baseEmoji: .maleJudge, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍⚖️" { - self.init(baseEmoji: .maleJudge, skinTones: [.dark]) - } else if rawValue == "👩‍⚖️" { - self.init(baseEmoji: .femaleJudge, skinTones: nil) - } else if rawValue == "👩🏻‍⚖️" { - self.init(baseEmoji: .femaleJudge, skinTones: [.light]) - } else if rawValue == "👩🏼‍⚖️" { - self.init(baseEmoji: .femaleJudge, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍⚖️" { - self.init(baseEmoji: .femaleJudge, skinTones: [.medium]) - } else if rawValue == "👩🏾‍⚖️" { - self.init(baseEmoji: .femaleJudge, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍⚖️" { - self.init(baseEmoji: .femaleJudge, skinTones: [.dark]) - } else if rawValue == "🧑‍🌾" { - self.init(baseEmoji: .farmer, skinTones: nil) - } else if rawValue == "🧑🏻‍🌾" { - self.init(baseEmoji: .farmer, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🌾" { - self.init(baseEmoji: .farmer, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🌾" { - self.init(baseEmoji: .farmer, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🌾" { - self.init(baseEmoji: .farmer, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🌾" { - self.init(baseEmoji: .farmer, skinTones: [.dark]) - } else if rawValue == "👨‍🌾" { - self.init(baseEmoji: .maleFarmer, skinTones: nil) - } else if rawValue == "👨🏻‍🌾" { - self.init(baseEmoji: .maleFarmer, skinTones: [.light]) - } else if rawValue == "👨🏼‍🌾" { - self.init(baseEmoji: .maleFarmer, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🌾" { - self.init(baseEmoji: .maleFarmer, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🌾" { - self.init(baseEmoji: .maleFarmer, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🌾" { - self.init(baseEmoji: .maleFarmer, skinTones: [.dark]) - } else if rawValue == "👩‍🌾" { - self.init(baseEmoji: .femaleFarmer, skinTones: nil) - } else if rawValue == "👩🏻‍🌾" { - self.init(baseEmoji: .femaleFarmer, skinTones: [.light]) - } else if rawValue == "👩🏼‍🌾" { - self.init(baseEmoji: .femaleFarmer, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🌾" { - self.init(baseEmoji: .femaleFarmer, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🌾" { - self.init(baseEmoji: .femaleFarmer, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🌾" { - self.init(baseEmoji: .femaleFarmer, skinTones: [.dark]) - } else if rawValue == "🧑‍🍳" { - self.init(baseEmoji: .cook, skinTones: nil) - } else if rawValue == "🧑🏻‍🍳" { - self.init(baseEmoji: .cook, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🍳" { - self.init(baseEmoji: .cook, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🍳" { - self.init(baseEmoji: .cook, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🍳" { - self.init(baseEmoji: .cook, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🍳" { - self.init(baseEmoji: .cook, skinTones: [.dark]) - } else if rawValue == "👨‍🍳" { - self.init(baseEmoji: .maleCook, skinTones: nil) - } else if rawValue == "👨🏻‍🍳" { - self.init(baseEmoji: .maleCook, skinTones: [.light]) - } else if rawValue == "👨🏼‍🍳" { - self.init(baseEmoji: .maleCook, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🍳" { - self.init(baseEmoji: .maleCook, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🍳" { - self.init(baseEmoji: .maleCook, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🍳" { - self.init(baseEmoji: .maleCook, skinTones: [.dark]) - } else if rawValue == "👩‍🍳" { - self.init(baseEmoji: .femaleCook, skinTones: nil) - } else if rawValue == "👩🏻‍🍳" { - self.init(baseEmoji: .femaleCook, skinTones: [.light]) - } else if rawValue == "👩🏼‍🍳" { - self.init(baseEmoji: .femaleCook, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🍳" { - self.init(baseEmoji: .femaleCook, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🍳" { - self.init(baseEmoji: .femaleCook, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🍳" { - self.init(baseEmoji: .femaleCook, skinTones: [.dark]) - } else if rawValue == "🧑‍🔧" { - self.init(baseEmoji: .mechanic, skinTones: nil) - } else if rawValue == "🧑🏻‍🔧" { - self.init(baseEmoji: .mechanic, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🔧" { - self.init(baseEmoji: .mechanic, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🔧" { - self.init(baseEmoji: .mechanic, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🔧" { - self.init(baseEmoji: .mechanic, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🔧" { - self.init(baseEmoji: .mechanic, skinTones: [.dark]) - } else if rawValue == "👨‍🔧" { - self.init(baseEmoji: .maleMechanic, skinTones: nil) - } else if rawValue == "👨🏻‍🔧" { - self.init(baseEmoji: .maleMechanic, skinTones: [.light]) - } else if rawValue == "👨🏼‍🔧" { - self.init(baseEmoji: .maleMechanic, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🔧" { - self.init(baseEmoji: .maleMechanic, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🔧" { - self.init(baseEmoji: .maleMechanic, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🔧" { - self.init(baseEmoji: .maleMechanic, skinTones: [.dark]) - } else if rawValue == "👩‍🔧" { - self.init(baseEmoji: .femaleMechanic, skinTones: nil) - } else if rawValue == "👩🏻‍🔧" { - self.init(baseEmoji: .femaleMechanic, skinTones: [.light]) - } else if rawValue == "👩🏼‍🔧" { - self.init(baseEmoji: .femaleMechanic, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🔧" { - self.init(baseEmoji: .femaleMechanic, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🔧" { - self.init(baseEmoji: .femaleMechanic, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🔧" { - self.init(baseEmoji: .femaleMechanic, skinTones: [.dark]) - } else if rawValue == "🧑‍🏭" { - self.init(baseEmoji: .factoryWorker, skinTones: nil) - } else if rawValue == "🧑🏻‍🏭" { - self.init(baseEmoji: .factoryWorker, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🏭" { - self.init(baseEmoji: .factoryWorker, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🏭" { - self.init(baseEmoji: .factoryWorker, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🏭" { - self.init(baseEmoji: .factoryWorker, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🏭" { - self.init(baseEmoji: .factoryWorker, skinTones: [.dark]) - } else if rawValue == "👨‍🏭" { - self.init(baseEmoji: .maleFactoryWorker, skinTones: nil) - } else if rawValue == "👨🏻‍🏭" { - self.init(baseEmoji: .maleFactoryWorker, skinTones: [.light]) - } else if rawValue == "👨🏼‍🏭" { - self.init(baseEmoji: .maleFactoryWorker, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🏭" { - self.init(baseEmoji: .maleFactoryWorker, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🏭" { - self.init(baseEmoji: .maleFactoryWorker, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🏭" { - self.init(baseEmoji: .maleFactoryWorker, skinTones: [.dark]) - } else if rawValue == "👩‍🏭" { - self.init(baseEmoji: .femaleFactoryWorker, skinTones: nil) - } else if rawValue == "👩🏻‍🏭" { - self.init(baseEmoji: .femaleFactoryWorker, skinTones: [.light]) - } else if rawValue == "👩🏼‍🏭" { - self.init(baseEmoji: .femaleFactoryWorker, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🏭" { - self.init(baseEmoji: .femaleFactoryWorker, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🏭" { - self.init(baseEmoji: .femaleFactoryWorker, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🏭" { - self.init(baseEmoji: .femaleFactoryWorker, skinTones: [.dark]) - } else if rawValue == "🧑‍💼" { - self.init(baseEmoji: .officeWorker, skinTones: nil) - } else if rawValue == "🧑🏻‍💼" { - self.init(baseEmoji: .officeWorker, skinTones: [.light]) - } else if rawValue == "🧑🏼‍💼" { - self.init(baseEmoji: .officeWorker, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍💼" { - self.init(baseEmoji: .officeWorker, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍💼" { - self.init(baseEmoji: .officeWorker, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍💼" { - self.init(baseEmoji: .officeWorker, skinTones: [.dark]) - } else if rawValue == "👨‍💼" { - self.init(baseEmoji: .maleOfficeWorker, skinTones: nil) - } else if rawValue == "👨🏻‍💼" { - self.init(baseEmoji: .maleOfficeWorker, skinTones: [.light]) - } else if rawValue == "👨🏼‍💼" { - self.init(baseEmoji: .maleOfficeWorker, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍💼" { - self.init(baseEmoji: .maleOfficeWorker, skinTones: [.medium]) - } else if rawValue == "👨🏾‍💼" { - self.init(baseEmoji: .maleOfficeWorker, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍💼" { - self.init(baseEmoji: .maleOfficeWorker, skinTones: [.dark]) - } else if rawValue == "👩‍💼" { - self.init(baseEmoji: .femaleOfficeWorker, skinTones: nil) - } else if rawValue == "👩🏻‍💼" { - self.init(baseEmoji: .femaleOfficeWorker, skinTones: [.light]) - } else if rawValue == "👩🏼‍💼" { - self.init(baseEmoji: .femaleOfficeWorker, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍💼" { - self.init(baseEmoji: .femaleOfficeWorker, skinTones: [.medium]) - } else if rawValue == "👩🏾‍💼" { - self.init(baseEmoji: .femaleOfficeWorker, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍💼" { - self.init(baseEmoji: .femaleOfficeWorker, skinTones: [.dark]) - } else if rawValue == "🧑‍🔬" { - self.init(baseEmoji: .scientist, skinTones: nil) - } else if rawValue == "🧑🏻‍🔬" { - self.init(baseEmoji: .scientist, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🔬" { - self.init(baseEmoji: .scientist, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🔬" { - self.init(baseEmoji: .scientist, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🔬" { - self.init(baseEmoji: .scientist, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🔬" { - self.init(baseEmoji: .scientist, skinTones: [.dark]) - } else if rawValue == "👨‍🔬" { - self.init(baseEmoji: .maleScientist, skinTones: nil) - } else if rawValue == "👨🏻‍🔬" { - self.init(baseEmoji: .maleScientist, skinTones: [.light]) - } else if rawValue == "👨🏼‍🔬" { - self.init(baseEmoji: .maleScientist, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🔬" { - self.init(baseEmoji: .maleScientist, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🔬" { - self.init(baseEmoji: .maleScientist, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🔬" { - self.init(baseEmoji: .maleScientist, skinTones: [.dark]) - } else if rawValue == "👩‍🔬" { - self.init(baseEmoji: .femaleScientist, skinTones: nil) - } else if rawValue == "👩🏻‍🔬" { - self.init(baseEmoji: .femaleScientist, skinTones: [.light]) - } else if rawValue == "👩🏼‍🔬" { - self.init(baseEmoji: .femaleScientist, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🔬" { - self.init(baseEmoji: .femaleScientist, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🔬" { - self.init(baseEmoji: .femaleScientist, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🔬" { - self.init(baseEmoji: .femaleScientist, skinTones: [.dark]) - } else if rawValue == "🧑‍💻" { - self.init(baseEmoji: .technologist, skinTones: nil) - } else if rawValue == "🧑🏻‍💻" { - self.init(baseEmoji: .technologist, skinTones: [.light]) - } else if rawValue == "🧑🏼‍💻" { - self.init(baseEmoji: .technologist, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍💻" { - self.init(baseEmoji: .technologist, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍💻" { - self.init(baseEmoji: .technologist, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍💻" { - self.init(baseEmoji: .technologist, skinTones: [.dark]) - } else if rawValue == "👨‍💻" { - self.init(baseEmoji: .maleTechnologist, skinTones: nil) - } else if rawValue == "👨🏻‍💻" { - self.init(baseEmoji: .maleTechnologist, skinTones: [.light]) - } else if rawValue == "👨🏼‍💻" { - self.init(baseEmoji: .maleTechnologist, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍💻" { - self.init(baseEmoji: .maleTechnologist, skinTones: [.medium]) - } else if rawValue == "👨🏾‍💻" { - self.init(baseEmoji: .maleTechnologist, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍💻" { - self.init(baseEmoji: .maleTechnologist, skinTones: [.dark]) - } else if rawValue == "👩‍💻" { - self.init(baseEmoji: .femaleTechnologist, skinTones: nil) - } else if rawValue == "👩🏻‍💻" { - self.init(baseEmoji: .femaleTechnologist, skinTones: [.light]) - } else if rawValue == "👩🏼‍💻" { - self.init(baseEmoji: .femaleTechnologist, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍💻" { - self.init(baseEmoji: .femaleTechnologist, skinTones: [.medium]) - } else if rawValue == "👩🏾‍💻" { - self.init(baseEmoji: .femaleTechnologist, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍💻" { - self.init(baseEmoji: .femaleTechnologist, skinTones: [.dark]) - } else if rawValue == "🧑‍🎤" { - self.init(baseEmoji: .singer, skinTones: nil) - } else if rawValue == "🧑🏻‍🎤" { - self.init(baseEmoji: .singer, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🎤" { - self.init(baseEmoji: .singer, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🎤" { - self.init(baseEmoji: .singer, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🎤" { - self.init(baseEmoji: .singer, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🎤" { - self.init(baseEmoji: .singer, skinTones: [.dark]) - } else if rawValue == "👨‍🎤" { - self.init(baseEmoji: .maleSinger, skinTones: nil) - } else if rawValue == "👨🏻‍🎤" { - self.init(baseEmoji: .maleSinger, skinTones: [.light]) - } else if rawValue == "👨🏼‍🎤" { - self.init(baseEmoji: .maleSinger, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🎤" { - self.init(baseEmoji: .maleSinger, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🎤" { - self.init(baseEmoji: .maleSinger, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🎤" { - self.init(baseEmoji: .maleSinger, skinTones: [.dark]) - } else if rawValue == "👩‍🎤" { - self.init(baseEmoji: .femaleSinger, skinTones: nil) - } else if rawValue == "👩🏻‍🎤" { - self.init(baseEmoji: .femaleSinger, skinTones: [.light]) - } else if rawValue == "👩🏼‍🎤" { - self.init(baseEmoji: .femaleSinger, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🎤" { - self.init(baseEmoji: .femaleSinger, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🎤" { - self.init(baseEmoji: .femaleSinger, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🎤" { - self.init(baseEmoji: .femaleSinger, skinTones: [.dark]) - } else if rawValue == "🧑‍🎨" { - self.init(baseEmoji: .artist, skinTones: nil) - } else if rawValue == "🧑🏻‍🎨" { - self.init(baseEmoji: .artist, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🎨" { - self.init(baseEmoji: .artist, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🎨" { - self.init(baseEmoji: .artist, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🎨" { - self.init(baseEmoji: .artist, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🎨" { - self.init(baseEmoji: .artist, skinTones: [.dark]) - } else if rawValue == "👨‍🎨" { - self.init(baseEmoji: .maleArtist, skinTones: nil) - } else if rawValue == "👨🏻‍🎨" { - self.init(baseEmoji: .maleArtist, skinTones: [.light]) - } else if rawValue == "👨🏼‍🎨" { - self.init(baseEmoji: .maleArtist, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🎨" { - self.init(baseEmoji: .maleArtist, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🎨" { - self.init(baseEmoji: .maleArtist, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🎨" { - self.init(baseEmoji: .maleArtist, skinTones: [.dark]) - } else if rawValue == "👩‍🎨" { - self.init(baseEmoji: .femaleArtist, skinTones: nil) - } else if rawValue == "👩🏻‍🎨" { - self.init(baseEmoji: .femaleArtist, skinTones: [.light]) - } else if rawValue == "👩🏼‍🎨" { - self.init(baseEmoji: .femaleArtist, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🎨" { - self.init(baseEmoji: .femaleArtist, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🎨" { - self.init(baseEmoji: .femaleArtist, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🎨" { - self.init(baseEmoji: .femaleArtist, skinTones: [.dark]) - } else if rawValue == "🧑‍✈️" { - self.init(baseEmoji: .pilot, skinTones: nil) - } else if rawValue == "🧑🏻‍✈️" { - self.init(baseEmoji: .pilot, skinTones: [.light]) - } else if rawValue == "🧑🏼‍✈️" { - self.init(baseEmoji: .pilot, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍✈️" { - self.init(baseEmoji: .pilot, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍✈️" { - self.init(baseEmoji: .pilot, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍✈️" { - self.init(baseEmoji: .pilot, skinTones: [.dark]) - } else if rawValue == "👨‍✈️" { - self.init(baseEmoji: .malePilot, skinTones: nil) - } else if rawValue == "👨🏻‍✈️" { - self.init(baseEmoji: .malePilot, skinTones: [.light]) - } else if rawValue == "👨🏼‍✈️" { - self.init(baseEmoji: .malePilot, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍✈️" { - self.init(baseEmoji: .malePilot, skinTones: [.medium]) - } else if rawValue == "👨🏾‍✈️" { - self.init(baseEmoji: .malePilot, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍✈️" { - self.init(baseEmoji: .malePilot, skinTones: [.dark]) - } else if rawValue == "👩‍✈️" { - self.init(baseEmoji: .femalePilot, skinTones: nil) - } else if rawValue == "👩🏻‍✈️" { - self.init(baseEmoji: .femalePilot, skinTones: [.light]) - } else if rawValue == "👩🏼‍✈️" { - self.init(baseEmoji: .femalePilot, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍✈️" { - self.init(baseEmoji: .femalePilot, skinTones: [.medium]) - } else if rawValue == "👩🏾‍✈️" { - self.init(baseEmoji: .femalePilot, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍✈️" { - self.init(baseEmoji: .femalePilot, skinTones: [.dark]) - } else if rawValue == "🧑‍🚀" { - self.init(baseEmoji: .astronaut, skinTones: nil) - } else if rawValue == "🧑🏻‍🚀" { - self.init(baseEmoji: .astronaut, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🚀" { - self.init(baseEmoji: .astronaut, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🚀" { - self.init(baseEmoji: .astronaut, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🚀" { - self.init(baseEmoji: .astronaut, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🚀" { - self.init(baseEmoji: .astronaut, skinTones: [.dark]) - } else if rawValue == "👨‍🚀" { - self.init(baseEmoji: .maleAstronaut, skinTones: nil) - } else if rawValue == "👨🏻‍🚀" { - self.init(baseEmoji: .maleAstronaut, skinTones: [.light]) - } else if rawValue == "👨🏼‍🚀" { - self.init(baseEmoji: .maleAstronaut, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🚀" { - self.init(baseEmoji: .maleAstronaut, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🚀" { - self.init(baseEmoji: .maleAstronaut, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🚀" { - self.init(baseEmoji: .maleAstronaut, skinTones: [.dark]) - } else if rawValue == "👩‍🚀" { - self.init(baseEmoji: .femaleAstronaut, skinTones: nil) - } else if rawValue == "👩🏻‍🚀" { - self.init(baseEmoji: .femaleAstronaut, skinTones: [.light]) - } else if rawValue == "👩🏼‍🚀" { - self.init(baseEmoji: .femaleAstronaut, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🚀" { - self.init(baseEmoji: .femaleAstronaut, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🚀" { - self.init(baseEmoji: .femaleAstronaut, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🚀" { - self.init(baseEmoji: .femaleAstronaut, skinTones: [.dark]) - } else if rawValue == "🧑‍🚒" { - self.init(baseEmoji: .firefighter, skinTones: nil) - } else if rawValue == "🧑🏻‍🚒" { - self.init(baseEmoji: .firefighter, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🚒" { - self.init(baseEmoji: .firefighter, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🚒" { - self.init(baseEmoji: .firefighter, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🚒" { - self.init(baseEmoji: .firefighter, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🚒" { - self.init(baseEmoji: .firefighter, skinTones: [.dark]) - } else if rawValue == "👨‍🚒" { - self.init(baseEmoji: .maleFirefighter, skinTones: nil) - } else if rawValue == "👨🏻‍🚒" { - self.init(baseEmoji: .maleFirefighter, skinTones: [.light]) - } else if rawValue == "👨🏼‍🚒" { - self.init(baseEmoji: .maleFirefighter, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🚒" { - self.init(baseEmoji: .maleFirefighter, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🚒" { - self.init(baseEmoji: .maleFirefighter, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🚒" { - self.init(baseEmoji: .maleFirefighter, skinTones: [.dark]) - } else if rawValue == "👩‍🚒" { - self.init(baseEmoji: .femaleFirefighter, skinTones: nil) - } else if rawValue == "👩🏻‍🚒" { - self.init(baseEmoji: .femaleFirefighter, skinTones: [.light]) - } else if rawValue == "👩🏼‍🚒" { - self.init(baseEmoji: .femaleFirefighter, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🚒" { - self.init(baseEmoji: .femaleFirefighter, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🚒" { - self.init(baseEmoji: .femaleFirefighter, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🚒" { - self.init(baseEmoji: .femaleFirefighter, skinTones: [.dark]) - } else if rawValue == "👮" { - self.init(baseEmoji: .cop, skinTones: nil) - } else if rawValue == "👮🏻" { - self.init(baseEmoji: .cop, skinTones: [.light]) - } else if rawValue == "👮🏼" { - self.init(baseEmoji: .cop, skinTones: [.mediumLight]) - } else if rawValue == "👮🏽" { - self.init(baseEmoji: .cop, skinTones: [.medium]) - } else if rawValue == "👮🏾" { - self.init(baseEmoji: .cop, skinTones: [.mediumDark]) - } else if rawValue == "👮🏿" { - self.init(baseEmoji: .cop, skinTones: [.dark]) - } else if rawValue == "👮‍♂️" { - self.init(baseEmoji: .malePoliceOfficer, skinTones: nil) - } else if rawValue == "👮🏻‍♂️" { - self.init(baseEmoji: .malePoliceOfficer, skinTones: [.light]) - } else if rawValue == "👮🏼‍♂️" { - self.init(baseEmoji: .malePoliceOfficer, skinTones: [.mediumLight]) - } else if rawValue == "👮🏽‍♂️" { - self.init(baseEmoji: .malePoliceOfficer, skinTones: [.medium]) - } else if rawValue == "👮🏾‍♂️" { - self.init(baseEmoji: .malePoliceOfficer, skinTones: [.mediumDark]) - } else if rawValue == "👮🏿‍♂️" { - self.init(baseEmoji: .malePoliceOfficer, skinTones: [.dark]) - } else if rawValue == "👮‍♀️" { - self.init(baseEmoji: .femalePoliceOfficer, skinTones: nil) - } else if rawValue == "👮🏻‍♀️" { - self.init(baseEmoji: .femalePoliceOfficer, skinTones: [.light]) - } else if rawValue == "👮🏼‍♀️" { - self.init(baseEmoji: .femalePoliceOfficer, skinTones: [.mediumLight]) - } else if rawValue == "👮🏽‍♀️" { - self.init(baseEmoji: .femalePoliceOfficer, skinTones: [.medium]) - } else if rawValue == "👮🏾‍♀️" { - self.init(baseEmoji: .femalePoliceOfficer, skinTones: [.mediumDark]) - } else if rawValue == "👮🏿‍♀️" { - self.init(baseEmoji: .femalePoliceOfficer, skinTones: [.dark]) - } else if rawValue == "🕵️" { - self.init(baseEmoji: .sleuthOrSpy, skinTones: nil) - } else if rawValue == "🕵🏻" { - self.init(baseEmoji: .sleuthOrSpy, skinTones: [.light]) - } else if rawValue == "🕵🏼" { - self.init(baseEmoji: .sleuthOrSpy, skinTones: [.mediumLight]) - } else if rawValue == "🕵🏽" { - self.init(baseEmoji: .sleuthOrSpy, skinTones: [.medium]) - } else if rawValue == "🕵🏾" { - self.init(baseEmoji: .sleuthOrSpy, skinTones: [.mediumDark]) - } else if rawValue == "🕵🏿" { - self.init(baseEmoji: .sleuthOrSpy, skinTones: [.dark]) - } else if rawValue == "🕵️‍♂️" { - self.init(baseEmoji: .maleDetective, skinTones: nil) - } else if rawValue == "🕵🏻‍♂️" { - self.init(baseEmoji: .maleDetective, skinTones: [.light]) - } else if rawValue == "🕵🏼‍♂️" { - self.init(baseEmoji: .maleDetective, skinTones: [.mediumLight]) - } else if rawValue == "🕵🏽‍♂️" { - self.init(baseEmoji: .maleDetective, skinTones: [.medium]) - } else if rawValue == "🕵🏾‍♂️" { - self.init(baseEmoji: .maleDetective, skinTones: [.mediumDark]) - } else if rawValue == "🕵🏿‍♂️" { - self.init(baseEmoji: .maleDetective, skinTones: [.dark]) - } else if rawValue == "🕵️‍♀️" { - self.init(baseEmoji: .femaleDetective, skinTones: nil) - } else if rawValue == "🕵🏻‍♀️" { - self.init(baseEmoji: .femaleDetective, skinTones: [.light]) - } else if rawValue == "🕵🏼‍♀️" { - self.init(baseEmoji: .femaleDetective, skinTones: [.mediumLight]) - } else if rawValue == "🕵🏽‍♀️" { - self.init(baseEmoji: .femaleDetective, skinTones: [.medium]) - } else if rawValue == "🕵🏾‍♀️" { - self.init(baseEmoji: .femaleDetective, skinTones: [.mediumDark]) - } else if rawValue == "🕵🏿‍♀️" { - self.init(baseEmoji: .femaleDetective, skinTones: [.dark]) - } else if rawValue == "💂" { - self.init(baseEmoji: .guardsman, skinTones: nil) - } else if rawValue == "💂🏻" { - self.init(baseEmoji: .guardsman, skinTones: [.light]) - } else if rawValue == "💂🏼" { - self.init(baseEmoji: .guardsman, skinTones: [.mediumLight]) - } else if rawValue == "💂🏽" { - self.init(baseEmoji: .guardsman, skinTones: [.medium]) - } else if rawValue == "💂🏾" { - self.init(baseEmoji: .guardsman, skinTones: [.mediumDark]) - } else if rawValue == "💂🏿" { - self.init(baseEmoji: .guardsman, skinTones: [.dark]) - } else if rawValue == "💂‍♂️" { - self.init(baseEmoji: .maleGuard, skinTones: nil) - } else if rawValue == "💂🏻‍♂️" { - self.init(baseEmoji: .maleGuard, skinTones: [.light]) - } else if rawValue == "💂🏼‍♂️" { - self.init(baseEmoji: .maleGuard, skinTones: [.mediumLight]) - } else if rawValue == "💂🏽‍♂️" { - self.init(baseEmoji: .maleGuard, skinTones: [.medium]) - } else if rawValue == "💂🏾‍♂️" { - self.init(baseEmoji: .maleGuard, skinTones: [.mediumDark]) - } else if rawValue == "💂🏿‍♂️" { - self.init(baseEmoji: .maleGuard, skinTones: [.dark]) - } else if rawValue == "💂‍♀️" { - self.init(baseEmoji: .femaleGuard, skinTones: nil) - } else if rawValue == "💂🏻‍♀️" { - self.init(baseEmoji: .femaleGuard, skinTones: [.light]) - } else if rawValue == "💂🏼‍♀️" { - self.init(baseEmoji: .femaleGuard, skinTones: [.mediumLight]) - } else if rawValue == "💂🏽‍♀️" { - self.init(baseEmoji: .femaleGuard, skinTones: [.medium]) - } else if rawValue == "💂🏾‍♀️" { - self.init(baseEmoji: .femaleGuard, skinTones: [.mediumDark]) - } else if rawValue == "💂🏿‍♀️" { - self.init(baseEmoji: .femaleGuard, skinTones: [.dark]) - } else if rawValue == "🥷" { - self.init(baseEmoji: .ninja, skinTones: nil) - } else if rawValue == "🥷🏻" { - self.init(baseEmoji: .ninja, skinTones: [.light]) - } else if rawValue == "🥷🏼" { - self.init(baseEmoji: .ninja, skinTones: [.mediumLight]) - } else if rawValue == "🥷🏽" { - self.init(baseEmoji: .ninja, skinTones: [.medium]) - } else if rawValue == "🥷🏾" { - self.init(baseEmoji: .ninja, skinTones: [.mediumDark]) - } else if rawValue == "🥷🏿" { - self.init(baseEmoji: .ninja, skinTones: [.dark]) - } else if rawValue == "👷" { - self.init(baseEmoji: .constructionWorker, skinTones: nil) - } else if rawValue == "👷🏻" { - self.init(baseEmoji: .constructionWorker, skinTones: [.light]) - } else if rawValue == "👷🏼" { - self.init(baseEmoji: .constructionWorker, skinTones: [.mediumLight]) - } else if rawValue == "👷🏽" { - self.init(baseEmoji: .constructionWorker, skinTones: [.medium]) - } else if rawValue == "👷🏾" { - self.init(baseEmoji: .constructionWorker, skinTones: [.mediumDark]) - } else if rawValue == "👷🏿" { - self.init(baseEmoji: .constructionWorker, skinTones: [.dark]) - } else if rawValue == "👷‍♂️" { - self.init(baseEmoji: .maleConstructionWorker, skinTones: nil) - } else if rawValue == "👷🏻‍♂️" { - self.init(baseEmoji: .maleConstructionWorker, skinTones: [.light]) - } else if rawValue == "👷🏼‍♂️" { - self.init(baseEmoji: .maleConstructionWorker, skinTones: [.mediumLight]) - } else if rawValue == "👷🏽‍♂️" { - self.init(baseEmoji: .maleConstructionWorker, skinTones: [.medium]) - } else if rawValue == "👷🏾‍♂️" { - self.init(baseEmoji: .maleConstructionWorker, skinTones: [.mediumDark]) - } else if rawValue == "👷🏿‍♂️" { - self.init(baseEmoji: .maleConstructionWorker, skinTones: [.dark]) - } else if rawValue == "👷‍♀️" { - self.init(baseEmoji: .femaleConstructionWorker, skinTones: nil) - } else if rawValue == "👷🏻‍♀️" { - self.init(baseEmoji: .femaleConstructionWorker, skinTones: [.light]) - } else if rawValue == "👷🏼‍♀️" { - self.init(baseEmoji: .femaleConstructionWorker, skinTones: [.mediumLight]) - } else if rawValue == "👷🏽‍♀️" { - self.init(baseEmoji: .femaleConstructionWorker, skinTones: [.medium]) - } else if rawValue == "👷🏾‍♀️" { - self.init(baseEmoji: .femaleConstructionWorker, skinTones: [.mediumDark]) - } else if rawValue == "👷🏿‍♀️" { - self.init(baseEmoji: .femaleConstructionWorker, skinTones: [.dark]) - } else if rawValue == "🫅" { - self.init(baseEmoji: .personWithCrown, skinTones: nil) - } else if rawValue == "🫅🏻" { - self.init(baseEmoji: .personWithCrown, skinTones: [.light]) - } else if rawValue == "🫅🏼" { - self.init(baseEmoji: .personWithCrown, skinTones: [.mediumLight]) - } else if rawValue == "🫅🏽" { - self.init(baseEmoji: .personWithCrown, skinTones: [.medium]) - } else if rawValue == "🫅🏾" { - self.init(baseEmoji: .personWithCrown, skinTones: [.mediumDark]) - } else if rawValue == "🫅🏿" { - self.init(baseEmoji: .personWithCrown, skinTones: [.dark]) - } else if rawValue == "🤴" { - self.init(baseEmoji: .prince, skinTones: nil) - } else if rawValue == "🤴🏻" { - self.init(baseEmoji: .prince, skinTones: [.light]) - } else if rawValue == "🤴🏼" { - self.init(baseEmoji: .prince, skinTones: [.mediumLight]) - } else if rawValue == "🤴🏽" { - self.init(baseEmoji: .prince, skinTones: [.medium]) - } else if rawValue == "🤴🏾" { - self.init(baseEmoji: .prince, skinTones: [.mediumDark]) - } else if rawValue == "🤴🏿" { - self.init(baseEmoji: .prince, skinTones: [.dark]) - } else if rawValue == "👸" { - self.init(baseEmoji: .princess, skinTones: nil) - } else if rawValue == "👸🏻" { - self.init(baseEmoji: .princess, skinTones: [.light]) - } else if rawValue == "👸🏼" { - self.init(baseEmoji: .princess, skinTones: [.mediumLight]) - } else if rawValue == "👸🏽" { - self.init(baseEmoji: .princess, skinTones: [.medium]) - } else if rawValue == "👸🏾" { - self.init(baseEmoji: .princess, skinTones: [.mediumDark]) - } else if rawValue == "👸🏿" { - self.init(baseEmoji: .princess, skinTones: [.dark]) - } else if rawValue == "👳" { - self.init(baseEmoji: .manWithTurban, skinTones: nil) - } else if rawValue == "👳🏻" { - self.init(baseEmoji: .manWithTurban, skinTones: [.light]) - } else if rawValue == "👳🏼" { - self.init(baseEmoji: .manWithTurban, skinTones: [.mediumLight]) - } else if rawValue == "👳🏽" { - self.init(baseEmoji: .manWithTurban, skinTones: [.medium]) - } else if rawValue == "👳🏾" { - self.init(baseEmoji: .manWithTurban, skinTones: [.mediumDark]) - } else if rawValue == "👳🏿" { - self.init(baseEmoji: .manWithTurban, skinTones: [.dark]) - } else if rawValue == "👳‍♂️" { - self.init(baseEmoji: .manWearingTurban, skinTones: nil) - } else if rawValue == "👳🏻‍♂️" { - self.init(baseEmoji: .manWearingTurban, skinTones: [.light]) - } else if rawValue == "👳🏼‍♂️" { - self.init(baseEmoji: .manWearingTurban, skinTones: [.mediumLight]) - } else if rawValue == "👳🏽‍♂️" { - self.init(baseEmoji: .manWearingTurban, skinTones: [.medium]) - } else if rawValue == "👳🏾‍♂️" { - self.init(baseEmoji: .manWearingTurban, skinTones: [.mediumDark]) - } else if rawValue == "👳🏿‍♂️" { - self.init(baseEmoji: .manWearingTurban, skinTones: [.dark]) - } else if rawValue == "👳‍♀️" { - self.init(baseEmoji: .womanWearingTurban, skinTones: nil) - } else if rawValue == "👳🏻‍♀️" { - self.init(baseEmoji: .womanWearingTurban, skinTones: [.light]) - } else if rawValue == "👳🏼‍♀️" { - self.init(baseEmoji: .womanWearingTurban, skinTones: [.mediumLight]) - } else if rawValue == "👳🏽‍♀️" { - self.init(baseEmoji: .womanWearingTurban, skinTones: [.medium]) - } else if rawValue == "👳🏾‍♀️" { - self.init(baseEmoji: .womanWearingTurban, skinTones: [.mediumDark]) - } else if rawValue == "👳🏿‍♀️" { - self.init(baseEmoji: .womanWearingTurban, skinTones: [.dark]) - } else if rawValue == "👲" { - self.init(baseEmoji: .manWithGuaPiMao, skinTones: nil) - } else if rawValue == "👲🏻" { - self.init(baseEmoji: .manWithGuaPiMao, skinTones: [.light]) - } else if rawValue == "👲🏼" { - self.init(baseEmoji: .manWithGuaPiMao, skinTones: [.mediumLight]) - } else if rawValue == "👲🏽" { - self.init(baseEmoji: .manWithGuaPiMao, skinTones: [.medium]) - } else if rawValue == "👲🏾" { - self.init(baseEmoji: .manWithGuaPiMao, skinTones: [.mediumDark]) - } else if rawValue == "👲🏿" { - self.init(baseEmoji: .manWithGuaPiMao, skinTones: [.dark]) - } else if rawValue == "🧕" { - self.init(baseEmoji: .personWithHeadscarf, skinTones: nil) - } else if rawValue == "🧕🏻" { - self.init(baseEmoji: .personWithHeadscarf, skinTones: [.light]) - } else if rawValue == "🧕🏼" { - self.init(baseEmoji: .personWithHeadscarf, skinTones: [.mediumLight]) - } else if rawValue == "🧕🏽" { - self.init(baseEmoji: .personWithHeadscarf, skinTones: [.medium]) - } else if rawValue == "🧕🏾" { - self.init(baseEmoji: .personWithHeadscarf, skinTones: [.mediumDark]) - } else if rawValue == "🧕🏿" { - self.init(baseEmoji: .personWithHeadscarf, skinTones: [.dark]) - } else if rawValue == "🤵" { - self.init(baseEmoji: .personInTuxedo, skinTones: nil) - } else if rawValue == "🤵🏻" { - self.init(baseEmoji: .personInTuxedo, skinTones: [.light]) - } else if rawValue == "🤵🏼" { - self.init(baseEmoji: .personInTuxedo, skinTones: [.mediumLight]) - } else if rawValue == "🤵🏽" { - self.init(baseEmoji: .personInTuxedo, skinTones: [.medium]) - } else if rawValue == "🤵🏾" { - self.init(baseEmoji: .personInTuxedo, skinTones: [.mediumDark]) - } else if rawValue == "🤵🏿" { - self.init(baseEmoji: .personInTuxedo, skinTones: [.dark]) - } else if rawValue == "🤵‍♂️" { - self.init(baseEmoji: .manInTuxedo, skinTones: nil) - } else if rawValue == "🤵🏻‍♂️" { - self.init(baseEmoji: .manInTuxedo, skinTones: [.light]) - } else if rawValue == "🤵🏼‍♂️" { - self.init(baseEmoji: .manInTuxedo, skinTones: [.mediumLight]) - } else if rawValue == "🤵🏽‍♂️" { - self.init(baseEmoji: .manInTuxedo, skinTones: [.medium]) - } else if rawValue == "🤵🏾‍♂️" { - self.init(baseEmoji: .manInTuxedo, skinTones: [.mediumDark]) - } else if rawValue == "🤵🏿‍♂️" { - self.init(baseEmoji: .manInTuxedo, skinTones: [.dark]) - } else if rawValue == "🤵‍♀️" { - self.init(baseEmoji: .womanInTuxedo, skinTones: nil) - } else if rawValue == "🤵🏻‍♀️" { - self.init(baseEmoji: .womanInTuxedo, skinTones: [.light]) - } else if rawValue == "🤵🏼‍♀️" { - self.init(baseEmoji: .womanInTuxedo, skinTones: [.mediumLight]) - } else if rawValue == "🤵🏽‍♀️" { - self.init(baseEmoji: .womanInTuxedo, skinTones: [.medium]) - } else if rawValue == "🤵🏾‍♀️" { - self.init(baseEmoji: .womanInTuxedo, skinTones: [.mediumDark]) - } else if rawValue == "🤵🏿‍♀️" { - self.init(baseEmoji: .womanInTuxedo, skinTones: [.dark]) - } else if rawValue == "👰" { - self.init(baseEmoji: .brideWithVeil, skinTones: nil) - } else if rawValue == "👰🏻" { - self.init(baseEmoji: .brideWithVeil, skinTones: [.light]) - } else if rawValue == "👰🏼" { - self.init(baseEmoji: .brideWithVeil, skinTones: [.mediumLight]) - } else if rawValue == "👰🏽" { - self.init(baseEmoji: .brideWithVeil, skinTones: [.medium]) - } else if rawValue == "👰🏾" { - self.init(baseEmoji: .brideWithVeil, skinTones: [.mediumDark]) - } else if rawValue == "👰🏿" { - self.init(baseEmoji: .brideWithVeil, skinTones: [.dark]) - } else if rawValue == "👰‍♂️" { - self.init(baseEmoji: .manWithVeil, skinTones: nil) - } else if rawValue == "👰🏻‍♂️" { - self.init(baseEmoji: .manWithVeil, skinTones: [.light]) - } else if rawValue == "👰🏼‍♂️" { - self.init(baseEmoji: .manWithVeil, skinTones: [.mediumLight]) - } else if rawValue == "👰🏽‍♂️" { - self.init(baseEmoji: .manWithVeil, skinTones: [.medium]) - } else if rawValue == "👰🏾‍♂️" { - self.init(baseEmoji: .manWithVeil, skinTones: [.mediumDark]) - } else if rawValue == "👰🏿‍♂️" { - self.init(baseEmoji: .manWithVeil, skinTones: [.dark]) - } else if rawValue == "👰‍♀️" { - self.init(baseEmoji: .womanWithVeil, skinTones: nil) - } else if rawValue == "👰🏻‍♀️" { - self.init(baseEmoji: .womanWithVeil, skinTones: [.light]) - } else if rawValue == "👰🏼‍♀️" { - self.init(baseEmoji: .womanWithVeil, skinTones: [.mediumLight]) - } else if rawValue == "👰🏽‍♀️" { - self.init(baseEmoji: .womanWithVeil, skinTones: [.medium]) - } else if rawValue == "👰🏾‍♀️" { - self.init(baseEmoji: .womanWithVeil, skinTones: [.mediumDark]) - } else if rawValue == "👰🏿‍♀️" { - self.init(baseEmoji: .womanWithVeil, skinTones: [.dark]) - } else if rawValue == "🤰" { - self.init(baseEmoji: .pregnantWoman, skinTones: nil) - } else if rawValue == "🤰🏻" { - self.init(baseEmoji: .pregnantWoman, skinTones: [.light]) - } else if rawValue == "🤰🏼" { - self.init(baseEmoji: .pregnantWoman, skinTones: [.mediumLight]) - } else if rawValue == "🤰🏽" { - self.init(baseEmoji: .pregnantWoman, skinTones: [.medium]) - } else if rawValue == "🤰🏾" { - self.init(baseEmoji: .pregnantWoman, skinTones: [.mediumDark]) - } else if rawValue == "🤰🏿" { - self.init(baseEmoji: .pregnantWoman, skinTones: [.dark]) - } else if rawValue == "🫃" { - self.init(baseEmoji: .pregnantMan, skinTones: nil) - } else if rawValue == "🫃🏻" { - self.init(baseEmoji: .pregnantMan, skinTones: [.light]) - } else if rawValue == "🫃🏼" { - self.init(baseEmoji: .pregnantMan, skinTones: [.mediumLight]) - } else if rawValue == "🫃🏽" { - self.init(baseEmoji: .pregnantMan, skinTones: [.medium]) - } else if rawValue == "🫃🏾" { - self.init(baseEmoji: .pregnantMan, skinTones: [.mediumDark]) - } else if rawValue == "🫃🏿" { - self.init(baseEmoji: .pregnantMan, skinTones: [.dark]) - } else if rawValue == "🫄" { - self.init(baseEmoji: .pregnantPerson, skinTones: nil) - } else if rawValue == "🫄🏻" { - self.init(baseEmoji: .pregnantPerson, skinTones: [.light]) - } else if rawValue == "🫄🏼" { - self.init(baseEmoji: .pregnantPerson, skinTones: [.mediumLight]) - } else if rawValue == "🫄🏽" { - self.init(baseEmoji: .pregnantPerson, skinTones: [.medium]) - } else if rawValue == "🫄🏾" { - self.init(baseEmoji: .pregnantPerson, skinTones: [.mediumDark]) - } else if rawValue == "🫄🏿" { - self.init(baseEmoji: .pregnantPerson, skinTones: [.dark]) - } else if rawValue == "🤱" { - self.init(baseEmoji: .breastFeeding, skinTones: nil) - } else if rawValue == "🤱🏻" { - self.init(baseEmoji: .breastFeeding, skinTones: [.light]) - } else if rawValue == "🤱🏼" { - self.init(baseEmoji: .breastFeeding, skinTones: [.mediumLight]) - } else if rawValue == "🤱🏽" { - self.init(baseEmoji: .breastFeeding, skinTones: [.medium]) - } else if rawValue == "🤱🏾" { - self.init(baseEmoji: .breastFeeding, skinTones: [.mediumDark]) - } else if rawValue == "🤱🏿" { - self.init(baseEmoji: .breastFeeding, skinTones: [.dark]) - } else if rawValue == "👩‍🍼" { - self.init(baseEmoji: .womanFeedingBaby, skinTones: nil) - } else if rawValue == "👩🏻‍🍼" { - self.init(baseEmoji: .womanFeedingBaby, skinTones: [.light]) - } else if rawValue == "👩🏼‍🍼" { - self.init(baseEmoji: .womanFeedingBaby, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🍼" { - self.init(baseEmoji: .womanFeedingBaby, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🍼" { - self.init(baseEmoji: .womanFeedingBaby, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🍼" { - self.init(baseEmoji: .womanFeedingBaby, skinTones: [.dark]) - } else if rawValue == "👨‍🍼" { - self.init(baseEmoji: .manFeedingBaby, skinTones: nil) - } else if rawValue == "👨🏻‍🍼" { - self.init(baseEmoji: .manFeedingBaby, skinTones: [.light]) - } else if rawValue == "👨🏼‍🍼" { - self.init(baseEmoji: .manFeedingBaby, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🍼" { - self.init(baseEmoji: .manFeedingBaby, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🍼" { - self.init(baseEmoji: .manFeedingBaby, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🍼" { - self.init(baseEmoji: .manFeedingBaby, skinTones: [.dark]) - } else if rawValue == "🧑‍🍼" { - self.init(baseEmoji: .personFeedingBaby, skinTones: nil) - } else if rawValue == "🧑🏻‍🍼" { - self.init(baseEmoji: .personFeedingBaby, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🍼" { - self.init(baseEmoji: .personFeedingBaby, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🍼" { - self.init(baseEmoji: .personFeedingBaby, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🍼" { - self.init(baseEmoji: .personFeedingBaby, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🍼" { - self.init(baseEmoji: .personFeedingBaby, skinTones: [.dark]) - } else if rawValue == "👼" { - self.init(baseEmoji: .angel, skinTones: nil) - } else if rawValue == "👼🏻" { - self.init(baseEmoji: .angel, skinTones: [.light]) - } else if rawValue == "👼🏼" { - self.init(baseEmoji: .angel, skinTones: [.mediumLight]) - } else if rawValue == "👼🏽" { - self.init(baseEmoji: .angel, skinTones: [.medium]) - } else if rawValue == "👼🏾" { - self.init(baseEmoji: .angel, skinTones: [.mediumDark]) - } else if rawValue == "👼🏿" { - self.init(baseEmoji: .angel, skinTones: [.dark]) - } else if rawValue == "🎅" { - self.init(baseEmoji: .santa, skinTones: nil) - } else if rawValue == "🎅🏻" { - self.init(baseEmoji: .santa, skinTones: [.light]) - } else if rawValue == "🎅🏼" { - self.init(baseEmoji: .santa, skinTones: [.mediumLight]) - } else if rawValue == "🎅🏽" { - self.init(baseEmoji: .santa, skinTones: [.medium]) - } else if rawValue == "🎅🏾" { - self.init(baseEmoji: .santa, skinTones: [.mediumDark]) - } else if rawValue == "🎅🏿" { - self.init(baseEmoji: .santa, skinTones: [.dark]) - } else if rawValue == "🤶" { - self.init(baseEmoji: .mrsClaus, skinTones: nil) - } else if rawValue == "🤶🏻" { - self.init(baseEmoji: .mrsClaus, skinTones: [.light]) - } else if rawValue == "🤶🏼" { - self.init(baseEmoji: .mrsClaus, skinTones: [.mediumLight]) - } else if rawValue == "🤶🏽" { - self.init(baseEmoji: .mrsClaus, skinTones: [.medium]) - } else if rawValue == "🤶🏾" { - self.init(baseEmoji: .mrsClaus, skinTones: [.mediumDark]) - } else if rawValue == "🤶🏿" { - self.init(baseEmoji: .mrsClaus, skinTones: [.dark]) - } else if rawValue == "🧑‍🎄" { - self.init(baseEmoji: .mxClaus, skinTones: nil) - } else if rawValue == "🧑🏻‍🎄" { - self.init(baseEmoji: .mxClaus, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🎄" { - self.init(baseEmoji: .mxClaus, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🎄" { - self.init(baseEmoji: .mxClaus, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🎄" { - self.init(baseEmoji: .mxClaus, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🎄" { - self.init(baseEmoji: .mxClaus, skinTones: [.dark]) - } else if rawValue == "🦸" { - self.init(baseEmoji: .superhero, skinTones: nil) - } else if rawValue == "🦸🏻" { - self.init(baseEmoji: .superhero, skinTones: [.light]) - } else if rawValue == "🦸🏼" { - self.init(baseEmoji: .superhero, skinTones: [.mediumLight]) - } else if rawValue == "🦸🏽" { - self.init(baseEmoji: .superhero, skinTones: [.medium]) - } else if rawValue == "🦸🏾" { - self.init(baseEmoji: .superhero, skinTones: [.mediumDark]) - } else if rawValue == "🦸🏿" { - self.init(baseEmoji: .superhero, skinTones: [.dark]) - } else if rawValue == "🦸‍♂️" { - self.init(baseEmoji: .maleSuperhero, skinTones: nil) - } else if rawValue == "🦸🏻‍♂️" { - self.init(baseEmoji: .maleSuperhero, skinTones: [.light]) - } else if rawValue == "🦸🏼‍♂️" { - self.init(baseEmoji: .maleSuperhero, skinTones: [.mediumLight]) - } else if rawValue == "🦸🏽‍♂️" { - self.init(baseEmoji: .maleSuperhero, skinTones: [.medium]) - } else if rawValue == "🦸🏾‍♂️" { - self.init(baseEmoji: .maleSuperhero, skinTones: [.mediumDark]) - } else if rawValue == "🦸🏿‍♂️" { - self.init(baseEmoji: .maleSuperhero, skinTones: [.dark]) - } else if rawValue == "🦸‍♀️" { - self.init(baseEmoji: .femaleSuperhero, skinTones: nil) - } else if rawValue == "🦸🏻‍♀️" { - self.init(baseEmoji: .femaleSuperhero, skinTones: [.light]) - } else if rawValue == "🦸🏼‍♀️" { - self.init(baseEmoji: .femaleSuperhero, skinTones: [.mediumLight]) - } else if rawValue == "🦸🏽‍♀️" { - self.init(baseEmoji: .femaleSuperhero, skinTones: [.medium]) - } else if rawValue == "🦸🏾‍♀️" { - self.init(baseEmoji: .femaleSuperhero, skinTones: [.mediumDark]) - } else if rawValue == "🦸🏿‍♀️" { - self.init(baseEmoji: .femaleSuperhero, skinTones: [.dark]) - } else if rawValue == "🦹" { - self.init(baseEmoji: .supervillain, skinTones: nil) - } else if rawValue == "🦹🏻" { - self.init(baseEmoji: .supervillain, skinTones: [.light]) - } else if rawValue == "🦹🏼" { - self.init(baseEmoji: .supervillain, skinTones: [.mediumLight]) - } else if rawValue == "🦹🏽" { - self.init(baseEmoji: .supervillain, skinTones: [.medium]) - } else if rawValue == "🦹🏾" { - self.init(baseEmoji: .supervillain, skinTones: [.mediumDark]) - } else if rawValue == "🦹🏿" { - self.init(baseEmoji: .supervillain, skinTones: [.dark]) - } else if rawValue == "🦹‍♂️" { - self.init(baseEmoji: .maleSupervillain, skinTones: nil) - } else if rawValue == "🦹🏻‍♂️" { - self.init(baseEmoji: .maleSupervillain, skinTones: [.light]) - } else if rawValue == "🦹🏼‍♂️" { - self.init(baseEmoji: .maleSupervillain, skinTones: [.mediumLight]) - } else if rawValue == "🦹🏽‍♂️" { - self.init(baseEmoji: .maleSupervillain, skinTones: [.medium]) - } else if rawValue == "🦹🏾‍♂️" { - self.init(baseEmoji: .maleSupervillain, skinTones: [.mediumDark]) - } else if rawValue == "🦹🏿‍♂️" { - self.init(baseEmoji: .maleSupervillain, skinTones: [.dark]) - } else if rawValue == "🦹‍♀️" { - self.init(baseEmoji: .femaleSupervillain, skinTones: nil) - } else if rawValue == "🦹🏻‍♀️" { - self.init(baseEmoji: .femaleSupervillain, skinTones: [.light]) - } else if rawValue == "🦹🏼‍♀️" { - self.init(baseEmoji: .femaleSupervillain, skinTones: [.mediumLight]) - } else if rawValue == "🦹🏽‍♀️" { - self.init(baseEmoji: .femaleSupervillain, skinTones: [.medium]) - } else if rawValue == "🦹🏾‍♀️" { - self.init(baseEmoji: .femaleSupervillain, skinTones: [.mediumDark]) - } else if rawValue == "🦹🏿‍♀️" { - self.init(baseEmoji: .femaleSupervillain, skinTones: [.dark]) - } else if rawValue == "🧙" { - self.init(baseEmoji: .mage, skinTones: nil) - } else if rawValue == "🧙🏻" { - self.init(baseEmoji: .mage, skinTones: [.light]) - } else if rawValue == "🧙🏼" { - self.init(baseEmoji: .mage, skinTones: [.mediumLight]) - } else if rawValue == "🧙🏽" { - self.init(baseEmoji: .mage, skinTones: [.medium]) - } else if rawValue == "🧙🏾" { - self.init(baseEmoji: .mage, skinTones: [.mediumDark]) - } else if rawValue == "🧙🏿" { - self.init(baseEmoji: .mage, skinTones: [.dark]) - } else if rawValue == "🧙‍♂️" { - self.init(baseEmoji: .maleMage, skinTones: nil) - } else if rawValue == "🧙🏻‍♂️" { - self.init(baseEmoji: .maleMage, skinTones: [.light]) - } else if rawValue == "🧙🏼‍♂️" { - self.init(baseEmoji: .maleMage, skinTones: [.mediumLight]) - } else if rawValue == "🧙🏽‍♂️" { - self.init(baseEmoji: .maleMage, skinTones: [.medium]) - } else if rawValue == "🧙🏾‍♂️" { - self.init(baseEmoji: .maleMage, skinTones: [.mediumDark]) - } else if rawValue == "🧙🏿‍♂️" { - self.init(baseEmoji: .maleMage, skinTones: [.dark]) - } else if rawValue == "🧙‍♀️" { - self.init(baseEmoji: .femaleMage, skinTones: nil) - } else if rawValue == "🧙🏻‍♀️" { - self.init(baseEmoji: .femaleMage, skinTones: [.light]) - } else if rawValue == "🧙🏼‍♀️" { - self.init(baseEmoji: .femaleMage, skinTones: [.mediumLight]) - } else if rawValue == "🧙🏽‍♀️" { - self.init(baseEmoji: .femaleMage, skinTones: [.medium]) - } else if rawValue == "🧙🏾‍♀️" { - self.init(baseEmoji: .femaleMage, skinTones: [.mediumDark]) - } else if rawValue == "🧙🏿‍♀️" { - self.init(baseEmoji: .femaleMage, skinTones: [.dark]) - } else if rawValue == "🧚" { - self.init(baseEmoji: .fairy, skinTones: nil) - } else if rawValue == "🧚🏻" { - self.init(baseEmoji: .fairy, skinTones: [.light]) - } else if rawValue == "🧚🏼" { - self.init(baseEmoji: .fairy, skinTones: [.mediumLight]) - } else if rawValue == "🧚🏽" { - self.init(baseEmoji: .fairy, skinTones: [.medium]) - } else if rawValue == "🧚🏾" { - self.init(baseEmoji: .fairy, skinTones: [.mediumDark]) - } else if rawValue == "🧚🏿" { - self.init(baseEmoji: .fairy, skinTones: [.dark]) - } else if rawValue == "🧚‍♂️" { - self.init(baseEmoji: .maleFairy, skinTones: nil) - } else if rawValue == "🧚🏻‍♂️" { - self.init(baseEmoji: .maleFairy, skinTones: [.light]) - } else if rawValue == "🧚🏼‍♂️" { - self.init(baseEmoji: .maleFairy, skinTones: [.mediumLight]) - } else if rawValue == "🧚🏽‍♂️" { - self.init(baseEmoji: .maleFairy, skinTones: [.medium]) - } else if rawValue == "🧚🏾‍♂️" { - self.init(baseEmoji: .maleFairy, skinTones: [.mediumDark]) - } else if rawValue == "🧚🏿‍♂️" { - self.init(baseEmoji: .maleFairy, skinTones: [.dark]) - } else if rawValue == "🧚‍♀️" { - self.init(baseEmoji: .femaleFairy, skinTones: nil) - } else if rawValue == "🧚🏻‍♀️" { - self.init(baseEmoji: .femaleFairy, skinTones: [.light]) - } else if rawValue == "🧚🏼‍♀️" { - self.init(baseEmoji: .femaleFairy, skinTones: [.mediumLight]) - } else if rawValue == "🧚🏽‍♀️" { - self.init(baseEmoji: .femaleFairy, skinTones: [.medium]) - } else if rawValue == "🧚🏾‍♀️" { - self.init(baseEmoji: .femaleFairy, skinTones: [.mediumDark]) - } else if rawValue == "🧚🏿‍♀️" { - self.init(baseEmoji: .femaleFairy, skinTones: [.dark]) - } else if rawValue == "🧛" { - self.init(baseEmoji: .vampire, skinTones: nil) - } else if rawValue == "🧛🏻" { - self.init(baseEmoji: .vampire, skinTones: [.light]) - } else if rawValue == "🧛🏼" { - self.init(baseEmoji: .vampire, skinTones: [.mediumLight]) - } else if rawValue == "🧛🏽" { - self.init(baseEmoji: .vampire, skinTones: [.medium]) - } else if rawValue == "🧛🏾" { - self.init(baseEmoji: .vampire, skinTones: [.mediumDark]) - } else if rawValue == "🧛🏿" { - self.init(baseEmoji: .vampire, skinTones: [.dark]) - } else if rawValue == "🧛‍♂️" { - self.init(baseEmoji: .maleVampire, skinTones: nil) - } else if rawValue == "🧛🏻‍♂️" { - self.init(baseEmoji: .maleVampire, skinTones: [.light]) - } else if rawValue == "🧛🏼‍♂️" { - self.init(baseEmoji: .maleVampire, skinTones: [.mediumLight]) - } else if rawValue == "🧛🏽‍♂️" { - self.init(baseEmoji: .maleVampire, skinTones: [.medium]) - } else if rawValue == "🧛🏾‍♂️" { - self.init(baseEmoji: .maleVampire, skinTones: [.mediumDark]) - } else if rawValue == "🧛🏿‍♂️" { - self.init(baseEmoji: .maleVampire, skinTones: [.dark]) - } else if rawValue == "🧛‍♀️" { - self.init(baseEmoji: .femaleVampire, skinTones: nil) - } else if rawValue == "🧛🏻‍♀️" { - self.init(baseEmoji: .femaleVampire, skinTones: [.light]) - } else if rawValue == "🧛🏼‍♀️" { - self.init(baseEmoji: .femaleVampire, skinTones: [.mediumLight]) - } else if rawValue == "🧛🏽‍♀️" { - self.init(baseEmoji: .femaleVampire, skinTones: [.medium]) - } else if rawValue == "🧛🏾‍♀️" { - self.init(baseEmoji: .femaleVampire, skinTones: [.mediumDark]) - } else if rawValue == "🧛🏿‍♀️" { - self.init(baseEmoji: .femaleVampire, skinTones: [.dark]) - } else if rawValue == "🧜" { - self.init(baseEmoji: .merperson, skinTones: nil) - } else if rawValue == "🧜🏻" { - self.init(baseEmoji: .merperson, skinTones: [.light]) - } else if rawValue == "🧜🏼" { - self.init(baseEmoji: .merperson, skinTones: [.mediumLight]) - } else if rawValue == "🧜🏽" { - self.init(baseEmoji: .merperson, skinTones: [.medium]) - } else if rawValue == "🧜🏾" { - self.init(baseEmoji: .merperson, skinTones: [.mediumDark]) - } else if rawValue == "🧜🏿" { - self.init(baseEmoji: .merperson, skinTones: [.dark]) - } else if rawValue == "🧜‍♂️" { - self.init(baseEmoji: .merman, skinTones: nil) - } else if rawValue == "🧜🏻‍♂️" { - self.init(baseEmoji: .merman, skinTones: [.light]) - } else if rawValue == "🧜🏼‍♂️" { - self.init(baseEmoji: .merman, skinTones: [.mediumLight]) - } else if rawValue == "🧜🏽‍♂️" { - self.init(baseEmoji: .merman, skinTones: [.medium]) - } else if rawValue == "🧜🏾‍♂️" { - self.init(baseEmoji: .merman, skinTones: [.mediumDark]) - } else if rawValue == "🧜🏿‍♂️" { - self.init(baseEmoji: .merman, skinTones: [.dark]) - } else if rawValue == "🧜‍♀️" { - self.init(baseEmoji: .mermaid, skinTones: nil) - } else if rawValue == "🧜🏻‍♀️" { - self.init(baseEmoji: .mermaid, skinTones: [.light]) - } else if rawValue == "🧜🏼‍♀️" { - self.init(baseEmoji: .mermaid, skinTones: [.mediumLight]) - } else if rawValue == "🧜🏽‍♀️" { - self.init(baseEmoji: .mermaid, skinTones: [.medium]) - } else if rawValue == "🧜🏾‍♀️" { - self.init(baseEmoji: .mermaid, skinTones: [.mediumDark]) - } else if rawValue == "🧜🏿‍♀️" { - self.init(baseEmoji: .mermaid, skinTones: [.dark]) - } else if rawValue == "🧝" { - self.init(baseEmoji: .elf, skinTones: nil) - } else if rawValue == "🧝🏻" { - self.init(baseEmoji: .elf, skinTones: [.light]) - } else if rawValue == "🧝🏼" { - self.init(baseEmoji: .elf, skinTones: [.mediumLight]) - } else if rawValue == "🧝🏽" { - self.init(baseEmoji: .elf, skinTones: [.medium]) - } else if rawValue == "🧝🏾" { - self.init(baseEmoji: .elf, skinTones: [.mediumDark]) - } else if rawValue == "🧝🏿" { - self.init(baseEmoji: .elf, skinTones: [.dark]) - } else if rawValue == "🧝‍♂️" { - self.init(baseEmoji: .maleElf, skinTones: nil) - } else if rawValue == "🧝🏻‍♂️" { - self.init(baseEmoji: .maleElf, skinTones: [.light]) - } else if rawValue == "🧝🏼‍♂️" { - self.init(baseEmoji: .maleElf, skinTones: [.mediumLight]) - } else if rawValue == "🧝🏽‍♂️" { - self.init(baseEmoji: .maleElf, skinTones: [.medium]) - } else if rawValue == "🧝🏾‍♂️" { - self.init(baseEmoji: .maleElf, skinTones: [.mediumDark]) - } else if rawValue == "🧝🏿‍♂️" { - self.init(baseEmoji: .maleElf, skinTones: [.dark]) - } else if rawValue == "🧝‍♀️" { - self.init(baseEmoji: .femaleElf, skinTones: nil) - } else if rawValue == "🧝🏻‍♀️" { - self.init(baseEmoji: .femaleElf, skinTones: [.light]) - } else if rawValue == "🧝🏼‍♀️" { - self.init(baseEmoji: .femaleElf, skinTones: [.mediumLight]) - } else if rawValue == "🧝🏽‍♀️" { - self.init(baseEmoji: .femaleElf, skinTones: [.medium]) - } else if rawValue == "🧝🏾‍♀️" { - self.init(baseEmoji: .femaleElf, skinTones: [.mediumDark]) - } else if rawValue == "🧝🏿‍♀️" { - self.init(baseEmoji: .femaleElf, skinTones: [.dark]) - } else if rawValue == "🧞" { - self.init(baseEmoji: .genie, skinTones: nil) - } else if rawValue == "🧞‍♂️" { - self.init(baseEmoji: .maleGenie, skinTones: nil) - } else if rawValue == "🧞‍♀️" { - self.init(baseEmoji: .femaleGenie, skinTones: nil) - } else if rawValue == "🧟" { - self.init(baseEmoji: .zombie, skinTones: nil) - } else if rawValue == "🧟‍♂️" { - self.init(baseEmoji: .maleZombie, skinTones: nil) - } else if rawValue == "🧟‍♀️" { - self.init(baseEmoji: .femaleZombie, skinTones: nil) - } else if rawValue == "🧌" { - self.init(baseEmoji: .troll, skinTones: nil) - } else if rawValue == "💆" { - self.init(baseEmoji: .massage, skinTones: nil) - } else if rawValue == "💆🏻" { - self.init(baseEmoji: .massage, skinTones: [.light]) - } else if rawValue == "💆🏼" { - self.init(baseEmoji: .massage, skinTones: [.mediumLight]) - } else if rawValue == "💆🏽" { - self.init(baseEmoji: .massage, skinTones: [.medium]) - } else if rawValue == "💆🏾" { - self.init(baseEmoji: .massage, skinTones: [.mediumDark]) - } else if rawValue == "💆🏿" { - self.init(baseEmoji: .massage, skinTones: [.dark]) - } else if rawValue == "💆‍♂️" { - self.init(baseEmoji: .manGettingMassage, skinTones: nil) - } else if rawValue == "💆🏻‍♂️" { - self.init(baseEmoji: .manGettingMassage, skinTones: [.light]) - } else if rawValue == "💆🏼‍♂️" { - self.init(baseEmoji: .manGettingMassage, skinTones: [.mediumLight]) - } else if rawValue == "💆🏽‍♂️" { - self.init(baseEmoji: .manGettingMassage, skinTones: [.medium]) - } else if rawValue == "💆🏾‍♂️" { - self.init(baseEmoji: .manGettingMassage, skinTones: [.mediumDark]) - } else if rawValue == "💆🏿‍♂️" { - self.init(baseEmoji: .manGettingMassage, skinTones: [.dark]) - } else if rawValue == "💆‍♀️" { - self.init(baseEmoji: .womanGettingMassage, skinTones: nil) - } else if rawValue == "💆🏻‍♀️" { - self.init(baseEmoji: .womanGettingMassage, skinTones: [.light]) - } else if rawValue == "💆🏼‍♀️" { - self.init(baseEmoji: .womanGettingMassage, skinTones: [.mediumLight]) - } else if rawValue == "💆🏽‍♀️" { - self.init(baseEmoji: .womanGettingMassage, skinTones: [.medium]) - } else if rawValue == "💆🏾‍♀️" { - self.init(baseEmoji: .womanGettingMassage, skinTones: [.mediumDark]) - } else if rawValue == "💆🏿‍♀️" { - self.init(baseEmoji: .womanGettingMassage, skinTones: [.dark]) - } else if rawValue == "💇" { - self.init(baseEmoji: .haircut, skinTones: nil) - } else if rawValue == "💇🏻" { - self.init(baseEmoji: .haircut, skinTones: [.light]) - } else if rawValue == "💇🏼" { - self.init(baseEmoji: .haircut, skinTones: [.mediumLight]) - } else if rawValue == "💇🏽" { - self.init(baseEmoji: .haircut, skinTones: [.medium]) - } else if rawValue == "💇🏾" { - self.init(baseEmoji: .haircut, skinTones: [.mediumDark]) - } else if rawValue == "💇🏿" { - self.init(baseEmoji: .haircut, skinTones: [.dark]) - } else if rawValue == "💇‍♂️" { - self.init(baseEmoji: .manGettingHaircut, skinTones: nil) - } else if rawValue == "💇🏻‍♂️" { - self.init(baseEmoji: .manGettingHaircut, skinTones: [.light]) - } else if rawValue == "💇🏼‍♂️" { - self.init(baseEmoji: .manGettingHaircut, skinTones: [.mediumLight]) - } else if rawValue == "💇🏽‍♂️" { - self.init(baseEmoji: .manGettingHaircut, skinTones: [.medium]) - } else if rawValue == "💇🏾‍♂️" { - self.init(baseEmoji: .manGettingHaircut, skinTones: [.mediumDark]) - } else if rawValue == "💇🏿‍♂️" { - self.init(baseEmoji: .manGettingHaircut, skinTones: [.dark]) - } else if rawValue == "💇‍♀️" { - self.init(baseEmoji: .womanGettingHaircut, skinTones: nil) - } else if rawValue == "💇🏻‍♀️" { - self.init(baseEmoji: .womanGettingHaircut, skinTones: [.light]) - } else if rawValue == "💇🏼‍♀️" { - self.init(baseEmoji: .womanGettingHaircut, skinTones: [.mediumLight]) - } else if rawValue == "💇🏽‍♀️" { - self.init(baseEmoji: .womanGettingHaircut, skinTones: [.medium]) - } else if rawValue == "💇🏾‍♀️" { - self.init(baseEmoji: .womanGettingHaircut, skinTones: [.mediumDark]) - } else if rawValue == "💇🏿‍♀️" { - self.init(baseEmoji: .womanGettingHaircut, skinTones: [.dark]) - } else if rawValue == "🚶" { - self.init(baseEmoji: .walking, skinTones: nil) - } else if rawValue == "🚶🏻" { - self.init(baseEmoji: .walking, skinTones: [.light]) - } else if rawValue == "🚶🏼" { - self.init(baseEmoji: .walking, skinTones: [.mediumLight]) - } else if rawValue == "🚶🏽" { - self.init(baseEmoji: .walking, skinTones: [.medium]) - } else if rawValue == "🚶🏾" { - self.init(baseEmoji: .walking, skinTones: [.mediumDark]) - } else if rawValue == "🚶🏿" { - self.init(baseEmoji: .walking, skinTones: [.dark]) - } else if rawValue == "🚶‍♂️" { - self.init(baseEmoji: .manWalking, skinTones: nil) - } else if rawValue == "🚶🏻‍♂️" { - self.init(baseEmoji: .manWalking, skinTones: [.light]) - } else if rawValue == "🚶🏼‍♂️" { - self.init(baseEmoji: .manWalking, skinTones: [.mediumLight]) - } else if rawValue == "🚶🏽‍♂️" { - self.init(baseEmoji: .manWalking, skinTones: [.medium]) - } else if rawValue == "🚶🏾‍♂️" { - self.init(baseEmoji: .manWalking, skinTones: [.mediumDark]) - } else if rawValue == "🚶🏿‍♂️" { - self.init(baseEmoji: .manWalking, skinTones: [.dark]) - } else if rawValue == "🚶‍♀️" { - self.init(baseEmoji: .womanWalking, skinTones: nil) - } else if rawValue == "🚶🏻‍♀️" { - self.init(baseEmoji: .womanWalking, skinTones: [.light]) - } else if rawValue == "🚶🏼‍♀️" { - self.init(baseEmoji: .womanWalking, skinTones: [.mediumLight]) - } else if rawValue == "🚶🏽‍♀️" { - self.init(baseEmoji: .womanWalking, skinTones: [.medium]) - } else if rawValue == "🚶🏾‍♀️" { - self.init(baseEmoji: .womanWalking, skinTones: [.mediumDark]) - } else if rawValue == "🚶🏿‍♀️" { - self.init(baseEmoji: .womanWalking, skinTones: [.dark]) - } else if rawValue == "🧍" { - self.init(baseEmoji: .standingPerson, skinTones: nil) - } else if rawValue == "🧍🏻" { - self.init(baseEmoji: .standingPerson, skinTones: [.light]) - } else if rawValue == "🧍🏼" { - self.init(baseEmoji: .standingPerson, skinTones: [.mediumLight]) - } else if rawValue == "🧍🏽" { - self.init(baseEmoji: .standingPerson, skinTones: [.medium]) - } else if rawValue == "🧍🏾" { - self.init(baseEmoji: .standingPerson, skinTones: [.mediumDark]) - } else if rawValue == "🧍🏿" { - self.init(baseEmoji: .standingPerson, skinTones: [.dark]) - } else if rawValue == "🧍‍♂️" { - self.init(baseEmoji: .manStanding, skinTones: nil) - } else if rawValue == "🧍🏻‍♂️" { - self.init(baseEmoji: .manStanding, skinTones: [.light]) - } else if rawValue == "🧍🏼‍♂️" { - self.init(baseEmoji: .manStanding, skinTones: [.mediumLight]) - } else if rawValue == "🧍🏽‍♂️" { - self.init(baseEmoji: .manStanding, skinTones: [.medium]) - } else if rawValue == "🧍🏾‍♂️" { - self.init(baseEmoji: .manStanding, skinTones: [.mediumDark]) - } else if rawValue == "🧍🏿‍♂️" { - self.init(baseEmoji: .manStanding, skinTones: [.dark]) - } else if rawValue == "🧍‍♀️" { - self.init(baseEmoji: .womanStanding, skinTones: nil) - } else if rawValue == "🧍🏻‍♀️" { - self.init(baseEmoji: .womanStanding, skinTones: [.light]) - } else if rawValue == "🧍🏼‍♀️" { - self.init(baseEmoji: .womanStanding, skinTones: [.mediumLight]) - } else if rawValue == "🧍🏽‍♀️" { - self.init(baseEmoji: .womanStanding, skinTones: [.medium]) - } else if rawValue == "🧍🏾‍♀️" { - self.init(baseEmoji: .womanStanding, skinTones: [.mediumDark]) - } else if rawValue == "🧍🏿‍♀️" { - self.init(baseEmoji: .womanStanding, skinTones: [.dark]) - } else if rawValue == "🧎" { - self.init(baseEmoji: .kneelingPerson, skinTones: nil) - } else if rawValue == "🧎🏻" { - self.init(baseEmoji: .kneelingPerson, skinTones: [.light]) - } else if rawValue == "🧎🏼" { - self.init(baseEmoji: .kneelingPerson, skinTones: [.mediumLight]) - } else if rawValue == "🧎🏽" { - self.init(baseEmoji: .kneelingPerson, skinTones: [.medium]) - } else if rawValue == "🧎🏾" { - self.init(baseEmoji: .kneelingPerson, skinTones: [.mediumDark]) - } else if rawValue == "🧎🏿" { - self.init(baseEmoji: .kneelingPerson, skinTones: [.dark]) - } else if rawValue == "🧎‍♂️" { - self.init(baseEmoji: .manKneeling, skinTones: nil) - } else if rawValue == "🧎🏻‍♂️" { - self.init(baseEmoji: .manKneeling, skinTones: [.light]) - } else if rawValue == "🧎🏼‍♂️" { - self.init(baseEmoji: .manKneeling, skinTones: [.mediumLight]) - } else if rawValue == "🧎🏽‍♂️" { - self.init(baseEmoji: .manKneeling, skinTones: [.medium]) - } else if rawValue == "🧎🏾‍♂️" { - self.init(baseEmoji: .manKneeling, skinTones: [.mediumDark]) - } else if rawValue == "🧎🏿‍♂️" { - self.init(baseEmoji: .manKneeling, skinTones: [.dark]) - } else if rawValue == "🧎‍♀️" { - self.init(baseEmoji: .womanKneeling, skinTones: nil) - } else if rawValue == "🧎🏻‍♀️" { - self.init(baseEmoji: .womanKneeling, skinTones: [.light]) - } else if rawValue == "🧎🏼‍♀️" { - self.init(baseEmoji: .womanKneeling, skinTones: [.mediumLight]) - } else if rawValue == "🧎🏽‍♀️" { - self.init(baseEmoji: .womanKneeling, skinTones: [.medium]) - } else if rawValue == "🧎🏾‍♀️" { - self.init(baseEmoji: .womanKneeling, skinTones: [.mediumDark]) - } else if rawValue == "🧎🏿‍♀️" { - self.init(baseEmoji: .womanKneeling, skinTones: [.dark]) - } else if rawValue == "🧑‍🦯" { - self.init(baseEmoji: .personWithProbingCane, skinTones: nil) - } else if rawValue == "🧑🏻‍🦯" { - self.init(baseEmoji: .personWithProbingCane, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🦯" { - self.init(baseEmoji: .personWithProbingCane, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🦯" { - self.init(baseEmoji: .personWithProbingCane, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🦯" { - self.init(baseEmoji: .personWithProbingCane, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🦯" { - self.init(baseEmoji: .personWithProbingCane, skinTones: [.dark]) - } else if rawValue == "👨‍🦯" { - self.init(baseEmoji: .manWithProbingCane, skinTones: nil) - } else if rawValue == "👨🏻‍🦯" { - self.init(baseEmoji: .manWithProbingCane, skinTones: [.light]) - } else if rawValue == "👨🏼‍🦯" { - self.init(baseEmoji: .manWithProbingCane, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🦯" { - self.init(baseEmoji: .manWithProbingCane, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🦯" { - self.init(baseEmoji: .manWithProbingCane, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🦯" { - self.init(baseEmoji: .manWithProbingCane, skinTones: [.dark]) - } else if rawValue == "👩‍🦯" { - self.init(baseEmoji: .womanWithProbingCane, skinTones: nil) - } else if rawValue == "👩🏻‍🦯" { - self.init(baseEmoji: .womanWithProbingCane, skinTones: [.light]) - } else if rawValue == "👩🏼‍🦯" { - self.init(baseEmoji: .womanWithProbingCane, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🦯" { - self.init(baseEmoji: .womanWithProbingCane, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🦯" { - self.init(baseEmoji: .womanWithProbingCane, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🦯" { - self.init(baseEmoji: .womanWithProbingCane, skinTones: [.dark]) - } else if rawValue == "🧑‍🦼" { - self.init(baseEmoji: .personInMotorizedWheelchair, skinTones: nil) - } else if rawValue == "🧑🏻‍🦼" { - self.init(baseEmoji: .personInMotorizedWheelchair, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🦼" { - self.init(baseEmoji: .personInMotorizedWheelchair, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🦼" { - self.init(baseEmoji: .personInMotorizedWheelchair, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🦼" { - self.init(baseEmoji: .personInMotorizedWheelchair, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🦼" { - self.init(baseEmoji: .personInMotorizedWheelchair, skinTones: [.dark]) - } else if rawValue == "👨‍🦼" { - self.init(baseEmoji: .manInMotorizedWheelchair, skinTones: nil) - } else if rawValue == "👨🏻‍🦼" { - self.init(baseEmoji: .manInMotorizedWheelchair, skinTones: [.light]) - } else if rawValue == "👨🏼‍🦼" { - self.init(baseEmoji: .manInMotorizedWheelchair, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🦼" { - self.init(baseEmoji: .manInMotorizedWheelchair, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🦼" { - self.init(baseEmoji: .manInMotorizedWheelchair, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🦼" { - self.init(baseEmoji: .manInMotorizedWheelchair, skinTones: [.dark]) - } else if rawValue == "👩‍🦼" { - self.init(baseEmoji: .womanInMotorizedWheelchair, skinTones: nil) - } else if rawValue == "👩🏻‍🦼" { - self.init(baseEmoji: .womanInMotorizedWheelchair, skinTones: [.light]) - } else if rawValue == "👩🏼‍🦼" { - self.init(baseEmoji: .womanInMotorizedWheelchair, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🦼" { - self.init(baseEmoji: .womanInMotorizedWheelchair, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🦼" { - self.init(baseEmoji: .womanInMotorizedWheelchair, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🦼" { - self.init(baseEmoji: .womanInMotorizedWheelchair, skinTones: [.dark]) - } else if rawValue == "🧑‍🦽" { - self.init(baseEmoji: .personInManualWheelchair, skinTones: nil) - } else if rawValue == "🧑🏻‍🦽" { - self.init(baseEmoji: .personInManualWheelchair, skinTones: [.light]) - } else if rawValue == "🧑🏼‍🦽" { - self.init(baseEmoji: .personInManualWheelchair, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏽‍🦽" { - self.init(baseEmoji: .personInManualWheelchair, skinTones: [.medium]) - } else if rawValue == "🧑🏾‍🦽" { - self.init(baseEmoji: .personInManualWheelchair, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏿‍🦽" { - self.init(baseEmoji: .personInManualWheelchair, skinTones: [.dark]) - } else if rawValue == "👨‍🦽" { - self.init(baseEmoji: .manInManualWheelchair, skinTones: nil) - } else if rawValue == "👨🏻‍🦽" { - self.init(baseEmoji: .manInManualWheelchair, skinTones: [.light]) - } else if rawValue == "👨🏼‍🦽" { - self.init(baseEmoji: .manInManualWheelchair, skinTones: [.mediumLight]) - } else if rawValue == "👨🏽‍🦽" { - self.init(baseEmoji: .manInManualWheelchair, skinTones: [.medium]) - } else if rawValue == "👨🏾‍🦽" { - self.init(baseEmoji: .manInManualWheelchair, skinTones: [.mediumDark]) - } else if rawValue == "👨🏿‍🦽" { - self.init(baseEmoji: .manInManualWheelchair, skinTones: [.dark]) - } else if rawValue == "👩‍🦽" { - self.init(baseEmoji: .womanInManualWheelchair, skinTones: nil) - } else if rawValue == "👩🏻‍🦽" { - self.init(baseEmoji: .womanInManualWheelchair, skinTones: [.light]) - } else if rawValue == "👩🏼‍🦽" { - self.init(baseEmoji: .womanInManualWheelchair, skinTones: [.mediumLight]) - } else if rawValue == "👩🏽‍🦽" { - self.init(baseEmoji: .womanInManualWheelchair, skinTones: [.medium]) - } else if rawValue == "👩🏾‍🦽" { - self.init(baseEmoji: .womanInManualWheelchair, skinTones: [.mediumDark]) - } else if rawValue == "👩🏿‍🦽" { - self.init(baseEmoji: .womanInManualWheelchair, skinTones: [.dark]) - } else if rawValue == "🏃" { - self.init(baseEmoji: .runner, skinTones: nil) - } else if rawValue == "🏃🏻" { - self.init(baseEmoji: .runner, skinTones: [.light]) - } else if rawValue == "🏃🏼" { - self.init(baseEmoji: .runner, skinTones: [.mediumLight]) - } else if rawValue == "🏃🏽" { - self.init(baseEmoji: .runner, skinTones: [.medium]) - } else if rawValue == "🏃🏾" { - self.init(baseEmoji: .runner, skinTones: [.mediumDark]) - } else if rawValue == "🏃🏿" { - self.init(baseEmoji: .runner, skinTones: [.dark]) - } else if rawValue == "🏃‍♂️" { - self.init(baseEmoji: .manRunning, skinTones: nil) - } else if rawValue == "🏃🏻‍♂️" { - self.init(baseEmoji: .manRunning, skinTones: [.light]) - } else if rawValue == "🏃🏼‍♂️" { - self.init(baseEmoji: .manRunning, skinTones: [.mediumLight]) - } else if rawValue == "🏃🏽‍♂️" { - self.init(baseEmoji: .manRunning, skinTones: [.medium]) - } else if rawValue == "🏃🏾‍♂️" { - self.init(baseEmoji: .manRunning, skinTones: [.mediumDark]) - } else if rawValue == "🏃🏿‍♂️" { - self.init(baseEmoji: .manRunning, skinTones: [.dark]) - } else if rawValue == "🏃‍♀️" { - self.init(baseEmoji: .womanRunning, skinTones: nil) - } else if rawValue == "🏃🏻‍♀️" { - self.init(baseEmoji: .womanRunning, skinTones: [.light]) - } else if rawValue == "🏃🏼‍♀️" { - self.init(baseEmoji: .womanRunning, skinTones: [.mediumLight]) - } else if rawValue == "🏃🏽‍♀️" { - self.init(baseEmoji: .womanRunning, skinTones: [.medium]) - } else if rawValue == "🏃🏾‍♀️" { - self.init(baseEmoji: .womanRunning, skinTones: [.mediumDark]) - } else if rawValue == "🏃🏿‍♀️" { - self.init(baseEmoji: .womanRunning, skinTones: [.dark]) - } else if rawValue == "💃" { - self.init(baseEmoji: .dancer, skinTones: nil) - } else if rawValue == "💃🏻" { - self.init(baseEmoji: .dancer, skinTones: [.light]) - } else if rawValue == "💃🏼" { - self.init(baseEmoji: .dancer, skinTones: [.mediumLight]) - } else if rawValue == "💃🏽" { - self.init(baseEmoji: .dancer, skinTones: [.medium]) - } else if rawValue == "💃🏾" { - self.init(baseEmoji: .dancer, skinTones: [.mediumDark]) - } else if rawValue == "💃🏿" { - self.init(baseEmoji: .dancer, skinTones: [.dark]) - } else if rawValue == "🕺" { - self.init(baseEmoji: .manDancing, skinTones: nil) - } else if rawValue == "🕺🏻" { - self.init(baseEmoji: .manDancing, skinTones: [.light]) - } else if rawValue == "🕺🏼" { - self.init(baseEmoji: .manDancing, skinTones: [.mediumLight]) - } else if rawValue == "🕺🏽" { - self.init(baseEmoji: .manDancing, skinTones: [.medium]) - } else if rawValue == "🕺🏾" { - self.init(baseEmoji: .manDancing, skinTones: [.mediumDark]) - } else if rawValue == "🕺🏿" { - self.init(baseEmoji: .manDancing, skinTones: [.dark]) - } else if rawValue == "🕴️" { - self.init(baseEmoji: .manInBusinessSuitLevitating, skinTones: nil) - } else if rawValue == "🕴🏻" { - self.init(baseEmoji: .manInBusinessSuitLevitating, skinTones: [.light]) - } else if rawValue == "🕴🏼" { - self.init(baseEmoji: .manInBusinessSuitLevitating, skinTones: [.mediumLight]) - } else if rawValue == "🕴🏽" { - self.init(baseEmoji: .manInBusinessSuitLevitating, skinTones: [.medium]) - } else if rawValue == "🕴🏾" { - self.init(baseEmoji: .manInBusinessSuitLevitating, skinTones: [.mediumDark]) - } else if rawValue == "🕴🏿" { - self.init(baseEmoji: .manInBusinessSuitLevitating, skinTones: [.dark]) - } else if rawValue == "👯" { - self.init(baseEmoji: .dancers, skinTones: nil) - } else if rawValue == "👯‍♂️" { - self.init(baseEmoji: .menWithBunnyEarsPartying, skinTones: nil) - } else if rawValue == "👯‍♀️" { - self.init(baseEmoji: .womenWithBunnyEarsPartying, skinTones: nil) - } else if rawValue == "🧖" { - self.init(baseEmoji: .personInSteamyRoom, skinTones: nil) - } else if rawValue == "🧖🏻" { - self.init(baseEmoji: .personInSteamyRoom, skinTones: [.light]) - } else if rawValue == "🧖🏼" { - self.init(baseEmoji: .personInSteamyRoom, skinTones: [.mediumLight]) - } else if rawValue == "🧖🏽" { - self.init(baseEmoji: .personInSteamyRoom, skinTones: [.medium]) - } else if rawValue == "🧖🏾" { - self.init(baseEmoji: .personInSteamyRoom, skinTones: [.mediumDark]) - } else if rawValue == "🧖🏿" { - self.init(baseEmoji: .personInSteamyRoom, skinTones: [.dark]) - } else if rawValue == "🧖‍♂️" { - self.init(baseEmoji: .manInSteamyRoom, skinTones: nil) - } else if rawValue == "🧖🏻‍♂️" { - self.init(baseEmoji: .manInSteamyRoom, skinTones: [.light]) - } else if rawValue == "🧖🏼‍♂️" { - self.init(baseEmoji: .manInSteamyRoom, skinTones: [.mediumLight]) - } else if rawValue == "🧖🏽‍♂️" { - self.init(baseEmoji: .manInSteamyRoom, skinTones: [.medium]) - } else if rawValue == "🧖🏾‍♂️" { - self.init(baseEmoji: .manInSteamyRoom, skinTones: [.mediumDark]) - } else if rawValue == "🧖🏿‍♂️" { - self.init(baseEmoji: .manInSteamyRoom, skinTones: [.dark]) - } else if rawValue == "🧖‍♀️" { - self.init(baseEmoji: .womanInSteamyRoom, skinTones: nil) - } else if rawValue == "🧖🏻‍♀️" { - self.init(baseEmoji: .womanInSteamyRoom, skinTones: [.light]) - } else if rawValue == "🧖🏼‍♀️" { - self.init(baseEmoji: .womanInSteamyRoom, skinTones: [.mediumLight]) - } else if rawValue == "🧖🏽‍♀️" { - self.init(baseEmoji: .womanInSteamyRoom, skinTones: [.medium]) - } else if rawValue == "🧖🏾‍♀️" { - self.init(baseEmoji: .womanInSteamyRoom, skinTones: [.mediumDark]) - } else if rawValue == "🧖🏿‍♀️" { - self.init(baseEmoji: .womanInSteamyRoom, skinTones: [.dark]) - } else if rawValue == "🧗" { - self.init(baseEmoji: .personClimbing, skinTones: nil) - } else if rawValue == "🧗🏻" { - self.init(baseEmoji: .personClimbing, skinTones: [.light]) - } else if rawValue == "🧗🏼" { - self.init(baseEmoji: .personClimbing, skinTones: [.mediumLight]) - } else if rawValue == "🧗🏽" { - self.init(baseEmoji: .personClimbing, skinTones: [.medium]) - } else if rawValue == "🧗🏾" { - self.init(baseEmoji: .personClimbing, skinTones: [.mediumDark]) - } else if rawValue == "🧗🏿" { - self.init(baseEmoji: .personClimbing, skinTones: [.dark]) - } else if rawValue == "🧗‍♂️" { - self.init(baseEmoji: .manClimbing, skinTones: nil) - } else if rawValue == "🧗🏻‍♂️" { - self.init(baseEmoji: .manClimbing, skinTones: [.light]) - } else if rawValue == "🧗🏼‍♂️" { - self.init(baseEmoji: .manClimbing, skinTones: [.mediumLight]) - } else if rawValue == "🧗🏽‍♂️" { - self.init(baseEmoji: .manClimbing, skinTones: [.medium]) - } else if rawValue == "🧗🏾‍♂️" { - self.init(baseEmoji: .manClimbing, skinTones: [.mediumDark]) - } else if rawValue == "🧗🏿‍♂️" { - self.init(baseEmoji: .manClimbing, skinTones: [.dark]) - } else if rawValue == "🧗‍♀️" { - self.init(baseEmoji: .womanClimbing, skinTones: nil) - } else if rawValue == "🧗🏻‍♀️" { - self.init(baseEmoji: .womanClimbing, skinTones: [.light]) - } else if rawValue == "🧗🏼‍♀️" { - self.init(baseEmoji: .womanClimbing, skinTones: [.mediumLight]) - } else if rawValue == "🧗🏽‍♀️" { - self.init(baseEmoji: .womanClimbing, skinTones: [.medium]) - } else if rawValue == "🧗🏾‍♀️" { - self.init(baseEmoji: .womanClimbing, skinTones: [.mediumDark]) - } else if rawValue == "🧗🏿‍♀️" { - self.init(baseEmoji: .womanClimbing, skinTones: [.dark]) - } else if rawValue == "🤺" { - self.init(baseEmoji: .fencer, skinTones: nil) - } else if rawValue == "🏇" { - self.init(baseEmoji: .horseRacing, skinTones: nil) - } else if rawValue == "🏇🏻" { - self.init(baseEmoji: .horseRacing, skinTones: [.light]) - } else if rawValue == "🏇🏼" { - self.init(baseEmoji: .horseRacing, skinTones: [.mediumLight]) - } else if rawValue == "🏇🏽" { - self.init(baseEmoji: .horseRacing, skinTones: [.medium]) - } else if rawValue == "🏇🏾" { - self.init(baseEmoji: .horseRacing, skinTones: [.mediumDark]) - } else if rawValue == "🏇🏿" { - self.init(baseEmoji: .horseRacing, skinTones: [.dark]) - } else if rawValue == "⛷️" { - self.init(baseEmoji: .skier, skinTones: nil) - } else if rawValue == "🏂" { - self.init(baseEmoji: .snowboarder, skinTones: nil) - } else if rawValue == "🏂🏻" { - self.init(baseEmoji: .snowboarder, skinTones: [.light]) - } else if rawValue == "🏂🏼" { - self.init(baseEmoji: .snowboarder, skinTones: [.mediumLight]) - } else if rawValue == "🏂🏽" { - self.init(baseEmoji: .snowboarder, skinTones: [.medium]) - } else if rawValue == "🏂🏾" { - self.init(baseEmoji: .snowboarder, skinTones: [.mediumDark]) - } else if rawValue == "🏂🏿" { - self.init(baseEmoji: .snowboarder, skinTones: [.dark]) - } else if rawValue == "🏌️" { - self.init(baseEmoji: .golfer, skinTones: nil) - } else if rawValue == "🏌🏻" { - self.init(baseEmoji: .golfer, skinTones: [.light]) - } else if rawValue == "🏌🏼" { - self.init(baseEmoji: .golfer, skinTones: [.mediumLight]) - } else if rawValue == "🏌🏽" { - self.init(baseEmoji: .golfer, skinTones: [.medium]) - } else if rawValue == "🏌🏾" { - self.init(baseEmoji: .golfer, skinTones: [.mediumDark]) - } else if rawValue == "🏌🏿" { - self.init(baseEmoji: .golfer, skinTones: [.dark]) - } else if rawValue == "🏌️‍♂️" { - self.init(baseEmoji: .manGolfing, skinTones: nil) - } else if rawValue == "🏌🏻‍♂️" { - self.init(baseEmoji: .manGolfing, skinTones: [.light]) - } else if rawValue == "🏌🏼‍♂️" { - self.init(baseEmoji: .manGolfing, skinTones: [.mediumLight]) - } else if rawValue == "🏌🏽‍♂️" { - self.init(baseEmoji: .manGolfing, skinTones: [.medium]) - } else if rawValue == "🏌🏾‍♂️" { - self.init(baseEmoji: .manGolfing, skinTones: [.mediumDark]) - } else if rawValue == "🏌🏿‍♂️" { - self.init(baseEmoji: .manGolfing, skinTones: [.dark]) - } else if rawValue == "🏌️‍♀️" { - self.init(baseEmoji: .womanGolfing, skinTones: nil) - } else if rawValue == "🏌🏻‍♀️" { - self.init(baseEmoji: .womanGolfing, skinTones: [.light]) - } else if rawValue == "🏌🏼‍♀️" { - self.init(baseEmoji: .womanGolfing, skinTones: [.mediumLight]) - } else if rawValue == "🏌🏽‍♀️" { - self.init(baseEmoji: .womanGolfing, skinTones: [.medium]) - } else if rawValue == "🏌🏾‍♀️" { - self.init(baseEmoji: .womanGolfing, skinTones: [.mediumDark]) - } else if rawValue == "🏌🏿‍♀️" { - self.init(baseEmoji: .womanGolfing, skinTones: [.dark]) - } else if rawValue == "🏄" { - self.init(baseEmoji: .surfer, skinTones: nil) - } else if rawValue == "🏄🏻" { - self.init(baseEmoji: .surfer, skinTones: [.light]) - } else if rawValue == "🏄🏼" { - self.init(baseEmoji: .surfer, skinTones: [.mediumLight]) - } else if rawValue == "🏄🏽" { - self.init(baseEmoji: .surfer, skinTones: [.medium]) - } else if rawValue == "🏄🏾" { - self.init(baseEmoji: .surfer, skinTones: [.mediumDark]) - } else if rawValue == "🏄🏿" { - self.init(baseEmoji: .surfer, skinTones: [.dark]) - } else if rawValue == "🏄‍♂️" { - self.init(baseEmoji: .manSurfing, skinTones: nil) - } else if rawValue == "🏄🏻‍♂️" { - self.init(baseEmoji: .manSurfing, skinTones: [.light]) - } else if rawValue == "🏄🏼‍♂️" { - self.init(baseEmoji: .manSurfing, skinTones: [.mediumLight]) - } else if rawValue == "🏄🏽‍♂️" { - self.init(baseEmoji: .manSurfing, skinTones: [.medium]) - } else if rawValue == "🏄🏾‍♂️" { - self.init(baseEmoji: .manSurfing, skinTones: [.mediumDark]) - } else if rawValue == "🏄🏿‍♂️" { - self.init(baseEmoji: .manSurfing, skinTones: [.dark]) - } else if rawValue == "🏄‍♀️" { - self.init(baseEmoji: .womanSurfing, skinTones: nil) - } else if rawValue == "🏄🏻‍♀️" { - self.init(baseEmoji: .womanSurfing, skinTones: [.light]) - } else if rawValue == "🏄🏼‍♀️" { - self.init(baseEmoji: .womanSurfing, skinTones: [.mediumLight]) - } else if rawValue == "🏄🏽‍♀️" { - self.init(baseEmoji: .womanSurfing, skinTones: [.medium]) - } else if rawValue == "🏄🏾‍♀️" { - self.init(baseEmoji: .womanSurfing, skinTones: [.mediumDark]) - } else if rawValue == "🏄🏿‍♀️" { - self.init(baseEmoji: .womanSurfing, skinTones: [.dark]) - } else if rawValue == "🚣" { - self.init(baseEmoji: .rowboat, skinTones: nil) - } else if rawValue == "🚣🏻" { - self.init(baseEmoji: .rowboat, skinTones: [.light]) - } else if rawValue == "🚣🏼" { - self.init(baseEmoji: .rowboat, skinTones: [.mediumLight]) - } else if rawValue == "🚣🏽" { - self.init(baseEmoji: .rowboat, skinTones: [.medium]) - } else if rawValue == "🚣🏾" { - self.init(baseEmoji: .rowboat, skinTones: [.mediumDark]) - } else if rawValue == "🚣🏿" { - self.init(baseEmoji: .rowboat, skinTones: [.dark]) - } else if rawValue == "🚣‍♂️" { - self.init(baseEmoji: .manRowingBoat, skinTones: nil) - } else if rawValue == "🚣🏻‍♂️" { - self.init(baseEmoji: .manRowingBoat, skinTones: [.light]) - } else if rawValue == "🚣🏼‍♂️" { - self.init(baseEmoji: .manRowingBoat, skinTones: [.mediumLight]) - } else if rawValue == "🚣🏽‍♂️" { - self.init(baseEmoji: .manRowingBoat, skinTones: [.medium]) - } else if rawValue == "🚣🏾‍♂️" { - self.init(baseEmoji: .manRowingBoat, skinTones: [.mediumDark]) - } else if rawValue == "🚣🏿‍♂️" { - self.init(baseEmoji: .manRowingBoat, skinTones: [.dark]) - } else if rawValue == "🚣‍♀️" { - self.init(baseEmoji: .womanRowingBoat, skinTones: nil) - } else if rawValue == "🚣🏻‍♀️" { - self.init(baseEmoji: .womanRowingBoat, skinTones: [.light]) - } else if rawValue == "🚣🏼‍♀️" { - self.init(baseEmoji: .womanRowingBoat, skinTones: [.mediumLight]) - } else if rawValue == "🚣🏽‍♀️" { - self.init(baseEmoji: .womanRowingBoat, skinTones: [.medium]) - } else if rawValue == "🚣🏾‍♀️" { - self.init(baseEmoji: .womanRowingBoat, skinTones: [.mediumDark]) - } else if rawValue == "🚣🏿‍♀️" { - self.init(baseEmoji: .womanRowingBoat, skinTones: [.dark]) - } else if rawValue == "🏊" { - self.init(baseEmoji: .swimmer, skinTones: nil) - } else if rawValue == "🏊🏻" { - self.init(baseEmoji: .swimmer, skinTones: [.light]) - } else if rawValue == "🏊🏼" { - self.init(baseEmoji: .swimmer, skinTones: [.mediumLight]) - } else if rawValue == "🏊🏽" { - self.init(baseEmoji: .swimmer, skinTones: [.medium]) - } else if rawValue == "🏊🏾" { - self.init(baseEmoji: .swimmer, skinTones: [.mediumDark]) - } else if rawValue == "🏊🏿" { - self.init(baseEmoji: .swimmer, skinTones: [.dark]) - } else if rawValue == "🏊‍♂️" { - self.init(baseEmoji: .manSwimming, skinTones: nil) - } else if rawValue == "🏊🏻‍♂️" { - self.init(baseEmoji: .manSwimming, skinTones: [.light]) - } else if rawValue == "🏊🏼‍♂️" { - self.init(baseEmoji: .manSwimming, skinTones: [.mediumLight]) - } else if rawValue == "🏊🏽‍♂️" { - self.init(baseEmoji: .manSwimming, skinTones: [.medium]) - } else if rawValue == "🏊🏾‍♂️" { - self.init(baseEmoji: .manSwimming, skinTones: [.mediumDark]) - } else if rawValue == "🏊🏿‍♂️" { - self.init(baseEmoji: .manSwimming, skinTones: [.dark]) - } else if rawValue == "🏊‍♀️" { - self.init(baseEmoji: .womanSwimming, skinTones: nil) - } else if rawValue == "🏊🏻‍♀️" { - self.init(baseEmoji: .womanSwimming, skinTones: [.light]) - } else if rawValue == "🏊🏼‍♀️" { - self.init(baseEmoji: .womanSwimming, skinTones: [.mediumLight]) - } else if rawValue == "🏊🏽‍♀️" { - self.init(baseEmoji: .womanSwimming, skinTones: [.medium]) - } else if rawValue == "🏊🏾‍♀️" { - self.init(baseEmoji: .womanSwimming, skinTones: [.mediumDark]) - } else if rawValue == "🏊🏿‍♀️" { - self.init(baseEmoji: .womanSwimming, skinTones: [.dark]) - } else if rawValue == "⛹️" { - self.init(baseEmoji: .personWithBall, skinTones: nil) - } else if rawValue == "⛹🏻" { - self.init(baseEmoji: .personWithBall, skinTones: [.light]) - } else if rawValue == "⛹🏼" { - self.init(baseEmoji: .personWithBall, skinTones: [.mediumLight]) - } else if rawValue == "⛹🏽" { - self.init(baseEmoji: .personWithBall, skinTones: [.medium]) - } else if rawValue == "⛹🏾" { - self.init(baseEmoji: .personWithBall, skinTones: [.mediumDark]) - } else if rawValue == "⛹🏿" { - self.init(baseEmoji: .personWithBall, skinTones: [.dark]) - } else if rawValue == "⛹️‍♂️" { - self.init(baseEmoji: .manBouncingBall, skinTones: nil) - } else if rawValue == "⛹🏻‍♂️" { - self.init(baseEmoji: .manBouncingBall, skinTones: [.light]) - } else if rawValue == "⛹🏼‍♂️" { - self.init(baseEmoji: .manBouncingBall, skinTones: [.mediumLight]) - } else if rawValue == "⛹🏽‍♂️" { - self.init(baseEmoji: .manBouncingBall, skinTones: [.medium]) - } else if rawValue == "⛹🏾‍♂️" { - self.init(baseEmoji: .manBouncingBall, skinTones: [.mediumDark]) - } else if rawValue == "⛹🏿‍♂️" { - self.init(baseEmoji: .manBouncingBall, skinTones: [.dark]) - } else if rawValue == "⛹️‍♀️" { - self.init(baseEmoji: .womanBouncingBall, skinTones: nil) - } else if rawValue == "⛹🏻‍♀️" { - self.init(baseEmoji: .womanBouncingBall, skinTones: [.light]) - } else if rawValue == "⛹🏼‍♀️" { - self.init(baseEmoji: .womanBouncingBall, skinTones: [.mediumLight]) - } else if rawValue == "⛹🏽‍♀️" { - self.init(baseEmoji: .womanBouncingBall, skinTones: [.medium]) - } else if rawValue == "⛹🏾‍♀️" { - self.init(baseEmoji: .womanBouncingBall, skinTones: [.mediumDark]) - } else if rawValue == "⛹🏿‍♀️" { - self.init(baseEmoji: .womanBouncingBall, skinTones: [.dark]) - } else if rawValue == "🏋️" { - self.init(baseEmoji: .weightLifter, skinTones: nil) - } else if rawValue == "🏋🏻" { - self.init(baseEmoji: .weightLifter, skinTones: [.light]) - } else if rawValue == "🏋🏼" { - self.init(baseEmoji: .weightLifter, skinTones: [.mediumLight]) - } else if rawValue == "🏋🏽" { - self.init(baseEmoji: .weightLifter, skinTones: [.medium]) - } else if rawValue == "🏋🏾" { - self.init(baseEmoji: .weightLifter, skinTones: [.mediumDark]) - } else if rawValue == "🏋🏿" { - self.init(baseEmoji: .weightLifter, skinTones: [.dark]) - } else if rawValue == "🏋️‍♂️" { - self.init(baseEmoji: .manLiftingWeights, skinTones: nil) - } else if rawValue == "🏋🏻‍♂️" { - self.init(baseEmoji: .manLiftingWeights, skinTones: [.light]) - } else if rawValue == "🏋🏼‍♂️" { - self.init(baseEmoji: .manLiftingWeights, skinTones: [.mediumLight]) - } else if rawValue == "🏋🏽‍♂️" { - self.init(baseEmoji: .manLiftingWeights, skinTones: [.medium]) - } else if rawValue == "🏋🏾‍♂️" { - self.init(baseEmoji: .manLiftingWeights, skinTones: [.mediumDark]) - } else if rawValue == "🏋🏿‍♂️" { - self.init(baseEmoji: .manLiftingWeights, skinTones: [.dark]) - } else if rawValue == "🏋️‍♀️" { - self.init(baseEmoji: .womanLiftingWeights, skinTones: nil) - } else if rawValue == "🏋🏻‍♀️" { - self.init(baseEmoji: .womanLiftingWeights, skinTones: [.light]) - } else if rawValue == "🏋🏼‍♀️" { - self.init(baseEmoji: .womanLiftingWeights, skinTones: [.mediumLight]) - } else if rawValue == "🏋🏽‍♀️" { - self.init(baseEmoji: .womanLiftingWeights, skinTones: [.medium]) - } else if rawValue == "🏋🏾‍♀️" { - self.init(baseEmoji: .womanLiftingWeights, skinTones: [.mediumDark]) - } else if rawValue == "🏋🏿‍♀️" { - self.init(baseEmoji: .womanLiftingWeights, skinTones: [.dark]) - } else if rawValue == "🚴" { - self.init(baseEmoji: .bicyclist, skinTones: nil) - } else if rawValue == "🚴🏻" { - self.init(baseEmoji: .bicyclist, skinTones: [.light]) - } else if rawValue == "🚴🏼" { - self.init(baseEmoji: .bicyclist, skinTones: [.mediumLight]) - } else if rawValue == "🚴🏽" { - self.init(baseEmoji: .bicyclist, skinTones: [.medium]) - } else if rawValue == "🚴🏾" { - self.init(baseEmoji: .bicyclist, skinTones: [.mediumDark]) - } else if rawValue == "🚴🏿" { - self.init(baseEmoji: .bicyclist, skinTones: [.dark]) - } else if rawValue == "🚴‍♂️" { - self.init(baseEmoji: .manBiking, skinTones: nil) - } else if rawValue == "🚴🏻‍♂️" { - self.init(baseEmoji: .manBiking, skinTones: [.light]) - } else if rawValue == "🚴🏼‍♂️" { - self.init(baseEmoji: .manBiking, skinTones: [.mediumLight]) - } else if rawValue == "🚴🏽‍♂️" { - self.init(baseEmoji: .manBiking, skinTones: [.medium]) - } else if rawValue == "🚴🏾‍♂️" { - self.init(baseEmoji: .manBiking, skinTones: [.mediumDark]) - } else if rawValue == "🚴🏿‍♂️" { - self.init(baseEmoji: .manBiking, skinTones: [.dark]) - } else if rawValue == "🚴‍♀️" { - self.init(baseEmoji: .womanBiking, skinTones: nil) - } else if rawValue == "🚴🏻‍♀️" { - self.init(baseEmoji: .womanBiking, skinTones: [.light]) - } else if rawValue == "🚴🏼‍♀️" { - self.init(baseEmoji: .womanBiking, skinTones: [.mediumLight]) - } else if rawValue == "🚴🏽‍♀️" { - self.init(baseEmoji: .womanBiking, skinTones: [.medium]) - } else if rawValue == "🚴🏾‍♀️" { - self.init(baseEmoji: .womanBiking, skinTones: [.mediumDark]) - } else if rawValue == "🚴🏿‍♀️" { - self.init(baseEmoji: .womanBiking, skinTones: [.dark]) - } else if rawValue == "🚵" { - self.init(baseEmoji: .mountainBicyclist, skinTones: nil) - } else if rawValue == "🚵🏻" { - self.init(baseEmoji: .mountainBicyclist, skinTones: [.light]) - } else if rawValue == "🚵🏼" { - self.init(baseEmoji: .mountainBicyclist, skinTones: [.mediumLight]) - } else if rawValue == "🚵🏽" { - self.init(baseEmoji: .mountainBicyclist, skinTones: [.medium]) - } else if rawValue == "🚵🏾" { - self.init(baseEmoji: .mountainBicyclist, skinTones: [.mediumDark]) - } else if rawValue == "🚵🏿" { - self.init(baseEmoji: .mountainBicyclist, skinTones: [.dark]) - } else if rawValue == "🚵‍♂️" { - self.init(baseEmoji: .manMountainBiking, skinTones: nil) - } else if rawValue == "🚵🏻‍♂️" { - self.init(baseEmoji: .manMountainBiking, skinTones: [.light]) - } else if rawValue == "🚵🏼‍♂️" { - self.init(baseEmoji: .manMountainBiking, skinTones: [.mediumLight]) - } else if rawValue == "🚵🏽‍♂️" { - self.init(baseEmoji: .manMountainBiking, skinTones: [.medium]) - } else if rawValue == "🚵🏾‍♂️" { - self.init(baseEmoji: .manMountainBiking, skinTones: [.mediumDark]) - } else if rawValue == "🚵🏿‍♂️" { - self.init(baseEmoji: .manMountainBiking, skinTones: [.dark]) - } else if rawValue == "🚵‍♀️" { - self.init(baseEmoji: .womanMountainBiking, skinTones: nil) - } else if rawValue == "🚵🏻‍♀️" { - self.init(baseEmoji: .womanMountainBiking, skinTones: [.light]) - } else if rawValue == "🚵🏼‍♀️" { - self.init(baseEmoji: .womanMountainBiking, skinTones: [.mediumLight]) - } else if rawValue == "🚵🏽‍♀️" { - self.init(baseEmoji: .womanMountainBiking, skinTones: [.medium]) - } else if rawValue == "🚵🏾‍♀️" { - self.init(baseEmoji: .womanMountainBiking, skinTones: [.mediumDark]) - } else if rawValue == "🚵🏿‍♀️" { - self.init(baseEmoji: .womanMountainBiking, skinTones: [.dark]) - } else if rawValue == "🤸" { - self.init(baseEmoji: .personDoingCartwheel, skinTones: nil) - } else if rawValue == "🤸🏻" { - self.init(baseEmoji: .personDoingCartwheel, skinTones: [.light]) - } else if rawValue == "🤸🏼" { - self.init(baseEmoji: .personDoingCartwheel, skinTones: [.mediumLight]) - } else if rawValue == "🤸🏽" { - self.init(baseEmoji: .personDoingCartwheel, skinTones: [.medium]) - } else if rawValue == "🤸🏾" { - self.init(baseEmoji: .personDoingCartwheel, skinTones: [.mediumDark]) - } else if rawValue == "🤸🏿" { - self.init(baseEmoji: .personDoingCartwheel, skinTones: [.dark]) - } else if rawValue == "🤸‍♂️" { - self.init(baseEmoji: .manCartwheeling, skinTones: nil) - } else if rawValue == "🤸🏻‍♂️" { - self.init(baseEmoji: .manCartwheeling, skinTones: [.light]) - } else if rawValue == "🤸🏼‍♂️" { - self.init(baseEmoji: .manCartwheeling, skinTones: [.mediumLight]) - } else if rawValue == "🤸🏽‍♂️" { - self.init(baseEmoji: .manCartwheeling, skinTones: [.medium]) - } else if rawValue == "🤸🏾‍♂️" { - self.init(baseEmoji: .manCartwheeling, skinTones: [.mediumDark]) - } else if rawValue == "🤸🏿‍♂️" { - self.init(baseEmoji: .manCartwheeling, skinTones: [.dark]) - } else if rawValue == "🤸‍♀️" { - self.init(baseEmoji: .womanCartwheeling, skinTones: nil) - } else if rawValue == "🤸🏻‍♀️" { - self.init(baseEmoji: .womanCartwheeling, skinTones: [.light]) - } else if rawValue == "🤸🏼‍♀️" { - self.init(baseEmoji: .womanCartwheeling, skinTones: [.mediumLight]) - } else if rawValue == "🤸🏽‍♀️" { - self.init(baseEmoji: .womanCartwheeling, skinTones: [.medium]) - } else if rawValue == "🤸🏾‍♀️" { - self.init(baseEmoji: .womanCartwheeling, skinTones: [.mediumDark]) - } else if rawValue == "🤸🏿‍♀️" { - self.init(baseEmoji: .womanCartwheeling, skinTones: [.dark]) - } else if rawValue == "🤼" { - self.init(baseEmoji: .wrestlers, skinTones: nil) - } else if rawValue == "🤼‍♂️" { - self.init(baseEmoji: .manWrestling, skinTones: nil) - } else if rawValue == "🤼‍♀️" { - self.init(baseEmoji: .womanWrestling, skinTones: nil) - } else if rawValue == "🤽" { - self.init(baseEmoji: .waterPolo, skinTones: nil) - } else if rawValue == "🤽🏻" { - self.init(baseEmoji: .waterPolo, skinTones: [.light]) - } else if rawValue == "🤽🏼" { - self.init(baseEmoji: .waterPolo, skinTones: [.mediumLight]) - } else if rawValue == "🤽🏽" { - self.init(baseEmoji: .waterPolo, skinTones: [.medium]) - } else if rawValue == "🤽🏾" { - self.init(baseEmoji: .waterPolo, skinTones: [.mediumDark]) - } else if rawValue == "🤽🏿" { - self.init(baseEmoji: .waterPolo, skinTones: [.dark]) - } else if rawValue == "🤽‍♂️" { - self.init(baseEmoji: .manPlayingWaterPolo, skinTones: nil) - } else if rawValue == "🤽🏻‍♂️" { - self.init(baseEmoji: .manPlayingWaterPolo, skinTones: [.light]) - } else if rawValue == "🤽🏼‍♂️" { - self.init(baseEmoji: .manPlayingWaterPolo, skinTones: [.mediumLight]) - } else if rawValue == "🤽🏽‍♂️" { - self.init(baseEmoji: .manPlayingWaterPolo, skinTones: [.medium]) - } else if rawValue == "🤽🏾‍♂️" { - self.init(baseEmoji: .manPlayingWaterPolo, skinTones: [.mediumDark]) - } else if rawValue == "🤽🏿‍♂️" { - self.init(baseEmoji: .manPlayingWaterPolo, skinTones: [.dark]) - } else if rawValue == "🤽‍♀️" { - self.init(baseEmoji: .womanPlayingWaterPolo, skinTones: nil) - } else if rawValue == "🤽🏻‍♀️" { - self.init(baseEmoji: .womanPlayingWaterPolo, skinTones: [.light]) - } else if rawValue == "🤽🏼‍♀️" { - self.init(baseEmoji: .womanPlayingWaterPolo, skinTones: [.mediumLight]) - } else if rawValue == "🤽🏽‍♀️" { - self.init(baseEmoji: .womanPlayingWaterPolo, skinTones: [.medium]) - } else if rawValue == "🤽🏾‍♀️" { - self.init(baseEmoji: .womanPlayingWaterPolo, skinTones: [.mediumDark]) - } else if rawValue == "🤽🏿‍♀️" { - self.init(baseEmoji: .womanPlayingWaterPolo, skinTones: [.dark]) - } else if rawValue == "🤾" { - self.init(baseEmoji: .handball, skinTones: nil) - } else if rawValue == "🤾🏻" { - self.init(baseEmoji: .handball, skinTones: [.light]) - } else if rawValue == "🤾🏼" { - self.init(baseEmoji: .handball, skinTones: [.mediumLight]) - } else if rawValue == "🤾🏽" { - self.init(baseEmoji: .handball, skinTones: [.medium]) - } else if rawValue == "🤾🏾" { - self.init(baseEmoji: .handball, skinTones: [.mediumDark]) - } else if rawValue == "🤾🏿" { - self.init(baseEmoji: .handball, skinTones: [.dark]) - } else if rawValue == "🤾‍♂️" { - self.init(baseEmoji: .manPlayingHandball, skinTones: nil) - } else if rawValue == "🤾🏻‍♂️" { - self.init(baseEmoji: .manPlayingHandball, skinTones: [.light]) - } else if rawValue == "🤾🏼‍♂️" { - self.init(baseEmoji: .manPlayingHandball, skinTones: [.mediumLight]) - } else if rawValue == "🤾🏽‍♂️" { - self.init(baseEmoji: .manPlayingHandball, skinTones: [.medium]) - } else if rawValue == "🤾🏾‍♂️" { - self.init(baseEmoji: .manPlayingHandball, skinTones: [.mediumDark]) - } else if rawValue == "🤾🏿‍♂️" { - self.init(baseEmoji: .manPlayingHandball, skinTones: [.dark]) - } else if rawValue == "🤾‍♀️" { - self.init(baseEmoji: .womanPlayingHandball, skinTones: nil) - } else if rawValue == "🤾🏻‍♀️" { - self.init(baseEmoji: .womanPlayingHandball, skinTones: [.light]) - } else if rawValue == "🤾🏼‍♀️" { - self.init(baseEmoji: .womanPlayingHandball, skinTones: [.mediumLight]) - } else if rawValue == "🤾🏽‍♀️" { - self.init(baseEmoji: .womanPlayingHandball, skinTones: [.medium]) - } else if rawValue == "🤾🏾‍♀️" { - self.init(baseEmoji: .womanPlayingHandball, skinTones: [.mediumDark]) - } else if rawValue == "🤾🏿‍♀️" { - self.init(baseEmoji: .womanPlayingHandball, skinTones: [.dark]) - } else if rawValue == "🤹" { - self.init(baseEmoji: .juggling, skinTones: nil) - } else if rawValue == "🤹🏻" { - self.init(baseEmoji: .juggling, skinTones: [.light]) - } else if rawValue == "🤹🏼" { - self.init(baseEmoji: .juggling, skinTones: [.mediumLight]) - } else if rawValue == "🤹🏽" { - self.init(baseEmoji: .juggling, skinTones: [.medium]) - } else if rawValue == "🤹🏾" { - self.init(baseEmoji: .juggling, skinTones: [.mediumDark]) - } else if rawValue == "🤹🏿" { - self.init(baseEmoji: .juggling, skinTones: [.dark]) - } else if rawValue == "🤹‍♂️" { - self.init(baseEmoji: .manJuggling, skinTones: nil) - } else if rawValue == "🤹🏻‍♂️" { - self.init(baseEmoji: .manJuggling, skinTones: [.light]) - } else if rawValue == "🤹🏼‍♂️" { - self.init(baseEmoji: .manJuggling, skinTones: [.mediumLight]) - } else if rawValue == "🤹🏽‍♂️" { - self.init(baseEmoji: .manJuggling, skinTones: [.medium]) - } else if rawValue == "🤹🏾‍♂️" { - self.init(baseEmoji: .manJuggling, skinTones: [.mediumDark]) - } else if rawValue == "🤹🏿‍♂️" { - self.init(baseEmoji: .manJuggling, skinTones: [.dark]) - } else if rawValue == "🤹‍♀️" { - self.init(baseEmoji: .womanJuggling, skinTones: nil) - } else if rawValue == "🤹🏻‍♀️" { - self.init(baseEmoji: .womanJuggling, skinTones: [.light]) - } else if rawValue == "🤹🏼‍♀️" { - self.init(baseEmoji: .womanJuggling, skinTones: [.mediumLight]) - } else if rawValue == "🤹🏽‍♀️" { - self.init(baseEmoji: .womanJuggling, skinTones: [.medium]) - } else if rawValue == "🤹🏾‍♀️" { - self.init(baseEmoji: .womanJuggling, skinTones: [.mediumDark]) - } else if rawValue == "🤹🏿‍♀️" { - self.init(baseEmoji: .womanJuggling, skinTones: [.dark]) - } else if rawValue == "🧘" { - self.init(baseEmoji: .personInLotusPosition, skinTones: nil) - } else if rawValue == "🧘🏻" { - self.init(baseEmoji: .personInLotusPosition, skinTones: [.light]) - } else if rawValue == "🧘🏼" { - self.init(baseEmoji: .personInLotusPosition, skinTones: [.mediumLight]) - } else if rawValue == "🧘🏽" { - self.init(baseEmoji: .personInLotusPosition, skinTones: [.medium]) - } else if rawValue == "🧘🏾" { - self.init(baseEmoji: .personInLotusPosition, skinTones: [.mediumDark]) - } else if rawValue == "🧘🏿" { - self.init(baseEmoji: .personInLotusPosition, skinTones: [.dark]) - } else if rawValue == "🧘‍♂️" { - self.init(baseEmoji: .manInLotusPosition, skinTones: nil) - } else if rawValue == "🧘🏻‍♂️" { - self.init(baseEmoji: .manInLotusPosition, skinTones: [.light]) - } else if rawValue == "🧘🏼‍♂️" { - self.init(baseEmoji: .manInLotusPosition, skinTones: [.mediumLight]) - } else if rawValue == "🧘🏽‍♂️" { - self.init(baseEmoji: .manInLotusPosition, skinTones: [.medium]) - } else if rawValue == "🧘🏾‍♂️" { - self.init(baseEmoji: .manInLotusPosition, skinTones: [.mediumDark]) - } else if rawValue == "🧘🏿‍♂️" { - self.init(baseEmoji: .manInLotusPosition, skinTones: [.dark]) - } else if rawValue == "🧘‍♀️" { - self.init(baseEmoji: .womanInLotusPosition, skinTones: nil) - } else if rawValue == "🧘🏻‍♀️" { - self.init(baseEmoji: .womanInLotusPosition, skinTones: [.light]) - } else if rawValue == "🧘🏼‍♀️" { - self.init(baseEmoji: .womanInLotusPosition, skinTones: [.mediumLight]) - } else if rawValue == "🧘🏽‍♀️" { - self.init(baseEmoji: .womanInLotusPosition, skinTones: [.medium]) - } else if rawValue == "🧘🏾‍♀️" { - self.init(baseEmoji: .womanInLotusPosition, skinTones: [.mediumDark]) - } else if rawValue == "🧘🏿‍♀️" { - self.init(baseEmoji: .womanInLotusPosition, skinTones: [.dark]) - } else if rawValue == "🛀" { - self.init(baseEmoji: .bath, skinTones: nil) - } else if rawValue == "🛀🏻" { - self.init(baseEmoji: .bath, skinTones: [.light]) - } else if rawValue == "🛀🏼" { - self.init(baseEmoji: .bath, skinTones: [.mediumLight]) - } else if rawValue == "🛀🏽" { - self.init(baseEmoji: .bath, skinTones: [.medium]) - } else if rawValue == "🛀🏾" { - self.init(baseEmoji: .bath, skinTones: [.mediumDark]) - } else if rawValue == "🛀🏿" { - self.init(baseEmoji: .bath, skinTones: [.dark]) - } else if rawValue == "🛌" { - self.init(baseEmoji: .sleepingAccommodation, skinTones: nil) - } else if rawValue == "🛌🏻" { - self.init(baseEmoji: .sleepingAccommodation, skinTones: [.light]) - } else if rawValue == "🛌🏼" { - self.init(baseEmoji: .sleepingAccommodation, skinTones: [.mediumLight]) - } else if rawValue == "🛌🏽" { - self.init(baseEmoji: .sleepingAccommodation, skinTones: [.medium]) - } else if rawValue == "🛌🏾" { - self.init(baseEmoji: .sleepingAccommodation, skinTones: [.mediumDark]) - } else if rawValue == "🛌🏿" { - self.init(baseEmoji: .sleepingAccommodation, skinTones: [.dark]) - } else if rawValue == "🧑‍🤝‍🧑" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: nil) - } else if rawValue == "🧑🏻‍🤝‍🧑🏻" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.light]) - } else if rawValue == "🧑🏻‍🤝‍🧑🏼" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.light, .mediumLight]) - } else if rawValue == "🧑🏻‍🤝‍🧑🏽" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.light, .medium]) - } else if rawValue == "🧑🏻‍🤝‍🧑🏾" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.light, .mediumDark]) - } else if rawValue == "🧑🏻‍🤝‍🧑🏿" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.light, .dark]) - } else if rawValue == "🧑🏼‍🤝‍🧑🏼" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏼‍🤝‍🧑🏻" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.mediumLight, .light]) - } else if rawValue == "🧑🏼‍🤝‍🧑🏽" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.mediumLight, .medium]) - } else if rawValue == "🧑🏼‍🤝‍🧑🏾" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.mediumLight, .mediumDark]) - } else if rawValue == "🧑🏼‍🤝‍🧑🏿" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.mediumLight, .dark]) - } else if rawValue == "🧑🏽‍🤝‍🧑🏽" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.medium]) - } else if rawValue == "🧑🏽‍🤝‍🧑🏻" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.medium, .light]) - } else if rawValue == "🧑🏽‍🤝‍🧑🏼" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.medium, .mediumLight]) - } else if rawValue == "🧑🏽‍🤝‍🧑🏾" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.medium, .mediumDark]) - } else if rawValue == "🧑🏽‍🤝‍🧑🏿" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.medium, .dark]) - } else if rawValue == "🧑🏾‍🤝‍🧑🏾" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏾‍🤝‍🧑🏻" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.mediumDark, .light]) - } else if rawValue == "🧑🏾‍🤝‍🧑🏼" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.mediumDark, .mediumLight]) - } else if rawValue == "🧑🏾‍🤝‍🧑🏽" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.mediumDark, .medium]) - } else if rawValue == "🧑🏾‍🤝‍🧑🏿" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.mediumDark, .dark]) - } else if rawValue == "🧑🏿‍🤝‍🧑🏿" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.dark]) - } else if rawValue == "🧑🏿‍🤝‍🧑🏻" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.dark, .light]) - } else if rawValue == "🧑🏿‍🤝‍🧑🏼" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.dark, .mediumLight]) - } else if rawValue == "🧑🏿‍🤝‍🧑🏽" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.dark, .medium]) - } else if rawValue == "🧑🏿‍🤝‍🧑🏾" { - self.init(baseEmoji: .peopleHoldingHands, skinTones: [.dark, .mediumDark]) - } else if rawValue == "👭" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: nil) - } else if rawValue == "👭🏻" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.light]) - } else if rawValue == "👩🏻‍🤝‍👩🏼" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.light, .mediumLight]) - } else if rawValue == "👩🏻‍🤝‍👩🏽" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.light, .medium]) - } else if rawValue == "👩🏻‍🤝‍👩🏾" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.light, .mediumDark]) - } else if rawValue == "👩🏻‍🤝‍👩🏿" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.light, .dark]) - } else if rawValue == "👭🏼" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumLight]) - } else if rawValue == "👩🏼‍🤝‍👩🏻" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumLight, .light]) - } else if rawValue == "👩🏼‍🤝‍👩🏽" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumLight, .medium]) - } else if rawValue == "👩🏼‍🤝‍👩🏾" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumLight, .mediumDark]) - } else if rawValue == "👩🏼‍🤝‍👩🏿" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumLight, .dark]) - } else if rawValue == "👭🏽" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.medium]) - } else if rawValue == "👩🏽‍🤝‍👩🏻" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.medium, .light]) - } else if rawValue == "👩🏽‍🤝‍👩🏼" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.medium, .mediumLight]) - } else if rawValue == "👩🏽‍🤝‍👩🏾" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.medium, .mediumDark]) - } else if rawValue == "👩🏽‍🤝‍👩🏿" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.medium, .dark]) - } else if rawValue == "👭🏾" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumDark]) - } else if rawValue == "👩🏾‍🤝‍👩🏻" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumDark, .light]) - } else if rawValue == "👩🏾‍🤝‍👩🏼" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumDark, .mediumLight]) - } else if rawValue == "👩🏾‍🤝‍👩🏽" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumDark, .medium]) - } else if rawValue == "👩🏾‍🤝‍👩🏿" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumDark, .dark]) - } else if rawValue == "👭🏿" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.dark]) - } else if rawValue == "👩🏿‍🤝‍👩🏻" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.dark, .light]) - } else if rawValue == "👩🏿‍🤝‍👩🏼" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.dark, .mediumLight]) - } else if rawValue == "👩🏿‍🤝‍👩🏽" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.dark, .medium]) - } else if rawValue == "👩🏿‍🤝‍👩🏾" { - self.init(baseEmoji: .twoWomenHoldingHands, skinTones: [.dark, .mediumDark]) - } else if rawValue == "👫" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: nil) - } else if rawValue == "👫🏻" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.light]) - } else if rawValue == "👩🏻‍🤝‍👨🏼" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.light, .mediumLight]) - } else if rawValue == "👩🏻‍🤝‍👨🏽" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.light, .medium]) - } else if rawValue == "👩🏻‍🤝‍👨🏾" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.light, .mediumDark]) - } else if rawValue == "👩🏻‍🤝‍👨🏿" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.light, .dark]) - } else if rawValue == "👫🏼" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumLight]) - } else if rawValue == "👩🏼‍🤝‍👨🏻" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumLight, .light]) - } else if rawValue == "👩🏼‍🤝‍👨🏽" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumLight, .medium]) - } else if rawValue == "👩🏼‍🤝‍👨🏾" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumLight, .mediumDark]) - } else if rawValue == "👩🏼‍🤝‍👨🏿" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumLight, .dark]) - } else if rawValue == "👫🏽" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.medium]) - } else if rawValue == "👩🏽‍🤝‍👨🏻" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.medium, .light]) - } else if rawValue == "👩🏽‍🤝‍👨🏼" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.medium, .mediumLight]) - } else if rawValue == "👩🏽‍🤝‍👨🏾" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.medium, .mediumDark]) - } else if rawValue == "👩🏽‍🤝‍👨🏿" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.medium, .dark]) - } else if rawValue == "👫🏾" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumDark]) - } else if rawValue == "👩🏾‍🤝‍👨🏻" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumDark, .light]) - } else if rawValue == "👩🏾‍🤝‍👨🏼" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumDark, .mediumLight]) - } else if rawValue == "👩🏾‍🤝‍👨🏽" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumDark, .medium]) - } else if rawValue == "👩🏾‍🤝‍👨🏿" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumDark, .dark]) - } else if rawValue == "👫🏿" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.dark]) - } else if rawValue == "👩🏿‍🤝‍👨🏻" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.dark, .light]) - } else if rawValue == "👩🏿‍🤝‍👨🏼" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.dark, .mediumLight]) - } else if rawValue == "👩🏿‍🤝‍👨🏽" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.dark, .medium]) - } else if rawValue == "👩🏿‍🤝‍👨🏾" { - self.init(baseEmoji: .manAndWomanHoldingHands, skinTones: [.dark, .mediumDark]) - } else if rawValue == "👬" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: nil) - } else if rawValue == "👬🏻" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.light]) - } else if rawValue == "👨🏻‍🤝‍👨🏼" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.light, .mediumLight]) - } else if rawValue == "👨🏻‍🤝‍👨🏽" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.light, .medium]) - } else if rawValue == "👨🏻‍🤝‍👨🏾" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.light, .mediumDark]) - } else if rawValue == "👨🏻‍🤝‍👨🏿" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.light, .dark]) - } else if rawValue == "👬🏼" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumLight]) - } else if rawValue == "👨🏼‍🤝‍👨🏻" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumLight, .light]) - } else if rawValue == "👨🏼‍🤝‍👨🏽" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumLight, .medium]) - } else if rawValue == "👨🏼‍🤝‍👨🏾" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumLight, .mediumDark]) - } else if rawValue == "👨🏼‍🤝‍👨🏿" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumLight, .dark]) - } else if rawValue == "👬🏽" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.medium]) - } else if rawValue == "👨🏽‍🤝‍👨🏻" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.medium, .light]) - } else if rawValue == "👨🏽‍🤝‍👨🏼" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.medium, .mediumLight]) - } else if rawValue == "👨🏽‍🤝‍👨🏾" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.medium, .mediumDark]) - } else if rawValue == "👨🏽‍🤝‍👨🏿" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.medium, .dark]) - } else if rawValue == "👬🏾" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumDark]) - } else if rawValue == "👨🏾‍🤝‍👨🏻" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumDark, .light]) - } else if rawValue == "👨🏾‍🤝‍👨🏼" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumDark, .mediumLight]) - } else if rawValue == "👨🏾‍🤝‍👨🏽" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumDark, .medium]) - } else if rawValue == "👨🏾‍🤝‍👨🏿" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumDark, .dark]) - } else if rawValue == "👬🏿" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.dark]) - } else if rawValue == "👨🏿‍🤝‍👨🏻" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.dark, .light]) - } else if rawValue == "👨🏿‍🤝‍👨🏼" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.dark, .mediumLight]) - } else if rawValue == "👨🏿‍🤝‍👨🏽" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.dark, .medium]) - } else if rawValue == "👨🏿‍🤝‍👨🏾" { - self.init(baseEmoji: .twoMenHoldingHands, skinTones: [.dark, .mediumDark]) - } else if rawValue == "💏" { - self.init(baseEmoji: .personKissPerson, skinTones: nil) - } else if rawValue == "💏🏻" { - self.init(baseEmoji: .personKissPerson, skinTones: [.light]) - } else if rawValue == "🧑🏻‍❤️‍💋‍🧑🏼" { - self.init(baseEmoji: .personKissPerson, skinTones: [.light, .mediumLight]) - } else if rawValue == "🧑🏻‍❤️‍💋‍🧑🏽" { - self.init(baseEmoji: .personKissPerson, skinTones: [.light, .medium]) - } else if rawValue == "🧑🏻‍❤️‍💋‍🧑🏾" { - self.init(baseEmoji: .personKissPerson, skinTones: [.light, .mediumDark]) - } else if rawValue == "🧑🏻‍❤️‍💋‍🧑🏿" { - self.init(baseEmoji: .personKissPerson, skinTones: [.light, .dark]) - } else if rawValue == "💏🏼" { - self.init(baseEmoji: .personKissPerson, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏼‍❤️‍💋‍🧑🏻" { - self.init(baseEmoji: .personKissPerson, skinTones: [.mediumLight, .light]) - } else if rawValue == "🧑🏼‍❤️‍💋‍🧑🏽" { - self.init(baseEmoji: .personKissPerson, skinTones: [.mediumLight, .medium]) - } else if rawValue == "🧑🏼‍❤️‍💋‍🧑🏾" { - self.init(baseEmoji: .personKissPerson, skinTones: [.mediumLight, .mediumDark]) - } else if rawValue == "🧑🏼‍❤️‍💋‍🧑🏿" { - self.init(baseEmoji: .personKissPerson, skinTones: [.mediumLight, .dark]) - } else if rawValue == "💏🏽" { - self.init(baseEmoji: .personKissPerson, skinTones: [.medium]) - } else if rawValue == "🧑🏽‍❤️‍💋‍🧑🏻" { - self.init(baseEmoji: .personKissPerson, skinTones: [.medium, .light]) - } else if rawValue == "🧑🏽‍❤️‍💋‍🧑🏼" { - self.init(baseEmoji: .personKissPerson, skinTones: [.medium, .mediumLight]) - } else if rawValue == "🧑🏽‍❤️‍💋‍🧑🏾" { - self.init(baseEmoji: .personKissPerson, skinTones: [.medium, .mediumDark]) - } else if rawValue == "🧑🏽‍❤️‍💋‍🧑🏿" { - self.init(baseEmoji: .personKissPerson, skinTones: [.medium, .dark]) - } else if rawValue == "💏🏾" { - self.init(baseEmoji: .personKissPerson, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏾‍❤️‍💋‍🧑🏻" { - self.init(baseEmoji: .personKissPerson, skinTones: [.mediumDark, .light]) - } else if rawValue == "🧑🏾‍❤️‍💋‍🧑🏼" { - self.init(baseEmoji: .personKissPerson, skinTones: [.mediumDark, .mediumLight]) - } else if rawValue == "🧑🏾‍❤️‍💋‍🧑🏽" { - self.init(baseEmoji: .personKissPerson, skinTones: [.mediumDark, .medium]) - } else if rawValue == "🧑🏾‍❤️‍💋‍🧑🏿" { - self.init(baseEmoji: .personKissPerson, skinTones: [.mediumDark, .dark]) - } else if rawValue == "💏🏿" { - self.init(baseEmoji: .personKissPerson, skinTones: [.dark]) - } else if rawValue == "🧑🏿‍❤️‍💋‍🧑🏻" { - self.init(baseEmoji: .personKissPerson, skinTones: [.dark, .light]) - } else if rawValue == "🧑🏿‍❤️‍💋‍🧑🏼" { - self.init(baseEmoji: .personKissPerson, skinTones: [.dark, .mediumLight]) - } else if rawValue == "🧑🏿‍❤️‍💋‍🧑🏽" { - self.init(baseEmoji: .personKissPerson, skinTones: [.dark, .medium]) - } else if rawValue == "🧑🏿‍❤️‍💋‍🧑🏾" { - self.init(baseEmoji: .personKissPerson, skinTones: [.dark, .mediumDark]) - } else if rawValue == "👩‍❤️‍💋‍👨" { - self.init(baseEmoji: .womanKissMan, skinTones: nil) - } else if rawValue == "👩🏻‍❤️‍💋‍👨🏻" { - self.init(baseEmoji: .womanKissMan, skinTones: [.light]) - } else if rawValue == "👩🏻‍❤️‍💋‍👨🏼" { - self.init(baseEmoji: .womanKissMan, skinTones: [.light, .mediumLight]) - } else if rawValue == "👩🏻‍❤️‍💋‍👨🏽" { - self.init(baseEmoji: .womanKissMan, skinTones: [.light, .medium]) - } else if rawValue == "👩🏻‍❤️‍💋‍👨🏾" { - self.init(baseEmoji: .womanKissMan, skinTones: [.light, .mediumDark]) - } else if rawValue == "👩🏻‍❤️‍💋‍👨🏿" { - self.init(baseEmoji: .womanKissMan, skinTones: [.light, .dark]) - } else if rawValue == "👩🏼‍❤️‍💋‍👨🏼" { - self.init(baseEmoji: .womanKissMan, skinTones: [.mediumLight]) - } else if rawValue == "👩🏼‍❤️‍💋‍👨🏻" { - self.init(baseEmoji: .womanKissMan, skinTones: [.mediumLight, .light]) - } else if rawValue == "👩🏼‍❤️‍💋‍👨🏽" { - self.init(baseEmoji: .womanKissMan, skinTones: [.mediumLight, .medium]) - } else if rawValue == "👩🏼‍❤️‍💋‍👨🏾" { - self.init(baseEmoji: .womanKissMan, skinTones: [.mediumLight, .mediumDark]) - } else if rawValue == "👩🏼‍❤️‍💋‍👨🏿" { - self.init(baseEmoji: .womanKissMan, skinTones: [.mediumLight, .dark]) - } else if rawValue == "👩🏽‍❤️‍💋‍👨🏽" { - self.init(baseEmoji: .womanKissMan, skinTones: [.medium]) - } else if rawValue == "👩🏽‍❤️‍💋‍👨🏻" { - self.init(baseEmoji: .womanKissMan, skinTones: [.medium, .light]) - } else if rawValue == "👩🏽‍❤️‍💋‍👨🏼" { - self.init(baseEmoji: .womanKissMan, skinTones: [.medium, .mediumLight]) - } else if rawValue == "👩🏽‍❤️‍💋‍👨🏾" { - self.init(baseEmoji: .womanKissMan, skinTones: [.medium, .mediumDark]) - } else if rawValue == "👩🏽‍❤️‍💋‍👨🏿" { - self.init(baseEmoji: .womanKissMan, skinTones: [.medium, .dark]) - } else if rawValue == "👩🏾‍❤️‍💋‍👨🏾" { - self.init(baseEmoji: .womanKissMan, skinTones: [.mediumDark]) - } else if rawValue == "👩🏾‍❤️‍💋‍👨🏻" { - self.init(baseEmoji: .womanKissMan, skinTones: [.mediumDark, .light]) - } else if rawValue == "👩🏾‍❤️‍💋‍👨🏼" { - self.init(baseEmoji: .womanKissMan, skinTones: [.mediumDark, .mediumLight]) - } else if rawValue == "👩🏾‍❤️‍💋‍👨🏽" { - self.init(baseEmoji: .womanKissMan, skinTones: [.mediumDark, .medium]) - } else if rawValue == "👩🏾‍❤️‍💋‍👨🏿" { - self.init(baseEmoji: .womanKissMan, skinTones: [.mediumDark, .dark]) - } else if rawValue == "👩🏿‍❤️‍💋‍👨🏿" { - self.init(baseEmoji: .womanKissMan, skinTones: [.dark]) - } else if rawValue == "👩🏿‍❤️‍💋‍👨🏻" { - self.init(baseEmoji: .womanKissMan, skinTones: [.dark, .light]) - } else if rawValue == "👩🏿‍❤️‍💋‍👨🏼" { - self.init(baseEmoji: .womanKissMan, skinTones: [.dark, .mediumLight]) - } else if rawValue == "👩🏿‍❤️‍💋‍👨🏽" { - self.init(baseEmoji: .womanKissMan, skinTones: [.dark, .medium]) - } else if rawValue == "👩🏿‍❤️‍💋‍👨🏾" { - self.init(baseEmoji: .womanKissMan, skinTones: [.dark, .mediumDark]) - } else if rawValue == "👨‍❤️‍💋‍👨" { - self.init(baseEmoji: .manKissMan, skinTones: nil) - } else if rawValue == "👨🏻‍❤️‍💋‍👨🏻" { - self.init(baseEmoji: .manKissMan, skinTones: [.light]) - } else if rawValue == "👨🏻‍❤️‍💋‍👨🏼" { - self.init(baseEmoji: .manKissMan, skinTones: [.light, .mediumLight]) - } else if rawValue == "👨🏻‍❤️‍💋‍👨🏽" { - self.init(baseEmoji: .manKissMan, skinTones: [.light, .medium]) - } else if rawValue == "👨🏻‍❤️‍💋‍👨🏾" { - self.init(baseEmoji: .manKissMan, skinTones: [.light, .mediumDark]) - } else if rawValue == "👨🏻‍❤️‍💋‍👨🏿" { - self.init(baseEmoji: .manKissMan, skinTones: [.light, .dark]) - } else if rawValue == "👨🏼‍❤️‍💋‍👨🏼" { - self.init(baseEmoji: .manKissMan, skinTones: [.mediumLight]) - } else if rawValue == "👨🏼‍❤️‍💋‍👨🏻" { - self.init(baseEmoji: .manKissMan, skinTones: [.mediumLight, .light]) - } else if rawValue == "👨🏼‍❤️‍💋‍👨🏽" { - self.init(baseEmoji: .manKissMan, skinTones: [.mediumLight, .medium]) - } else if rawValue == "👨🏼‍❤️‍💋‍👨🏾" { - self.init(baseEmoji: .manKissMan, skinTones: [.mediumLight, .mediumDark]) - } else if rawValue == "👨🏼‍❤️‍💋‍👨🏿" { - self.init(baseEmoji: .manKissMan, skinTones: [.mediumLight, .dark]) - } else if rawValue == "👨🏽‍❤️‍💋‍👨🏽" { - self.init(baseEmoji: .manKissMan, skinTones: [.medium]) - } else if rawValue == "👨🏽‍❤️‍💋‍👨🏻" { - self.init(baseEmoji: .manKissMan, skinTones: [.medium, .light]) - } else if rawValue == "👨🏽‍❤️‍💋‍👨🏼" { - self.init(baseEmoji: .manKissMan, skinTones: [.medium, .mediumLight]) - } else if rawValue == "👨🏽‍❤️‍💋‍👨🏾" { - self.init(baseEmoji: .manKissMan, skinTones: [.medium, .mediumDark]) - } else if rawValue == "👨🏽‍❤️‍💋‍👨🏿" { - self.init(baseEmoji: .manKissMan, skinTones: [.medium, .dark]) - } else if rawValue == "👨🏾‍❤️‍💋‍👨🏾" { - self.init(baseEmoji: .manKissMan, skinTones: [.mediumDark]) - } else if rawValue == "👨🏾‍❤️‍💋‍👨🏻" { - self.init(baseEmoji: .manKissMan, skinTones: [.mediumDark, .light]) - } else if rawValue == "👨🏾‍❤️‍💋‍👨🏼" { - self.init(baseEmoji: .manKissMan, skinTones: [.mediumDark, .mediumLight]) - } else if rawValue == "👨🏾‍❤️‍💋‍👨🏽" { - self.init(baseEmoji: .manKissMan, skinTones: [.mediumDark, .medium]) - } else if rawValue == "👨🏾‍❤️‍💋‍👨🏿" { - self.init(baseEmoji: .manKissMan, skinTones: [.mediumDark, .dark]) - } else if rawValue == "👨🏿‍❤️‍💋‍👨🏿" { - self.init(baseEmoji: .manKissMan, skinTones: [.dark]) - } else if rawValue == "👨🏿‍❤️‍💋‍👨🏻" { - self.init(baseEmoji: .manKissMan, skinTones: [.dark, .light]) - } else if rawValue == "👨🏿‍❤️‍💋‍👨🏼" { - self.init(baseEmoji: .manKissMan, skinTones: [.dark, .mediumLight]) - } else if rawValue == "👨🏿‍❤️‍💋‍👨🏽" { - self.init(baseEmoji: .manKissMan, skinTones: [.dark, .medium]) - } else if rawValue == "👨🏿‍❤️‍💋‍👨🏾" { - self.init(baseEmoji: .manKissMan, skinTones: [.dark, .mediumDark]) - } else if rawValue == "👩‍❤️‍💋‍👩" { - self.init(baseEmoji: .womanKissWoman, skinTones: nil) - } else if rawValue == "👩🏻‍❤️‍💋‍👩🏻" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.light]) - } else if rawValue == "👩🏻‍❤️‍💋‍👩🏼" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.light, .mediumLight]) - } else if rawValue == "👩🏻‍❤️‍💋‍👩🏽" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.light, .medium]) - } else if rawValue == "👩🏻‍❤️‍💋‍👩🏾" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.light, .mediumDark]) - } else if rawValue == "👩🏻‍❤️‍💋‍👩🏿" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.light, .dark]) - } else if rawValue == "👩🏼‍❤️‍💋‍👩🏼" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.mediumLight]) - } else if rawValue == "👩🏼‍❤️‍💋‍👩🏻" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.mediumLight, .light]) - } else if rawValue == "👩🏼‍❤️‍💋‍👩🏽" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.mediumLight, .medium]) - } else if rawValue == "👩🏼‍❤️‍💋‍👩🏾" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.mediumLight, .mediumDark]) - } else if rawValue == "👩🏼‍❤️‍💋‍👩🏿" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.mediumLight, .dark]) - } else if rawValue == "👩🏽‍❤️‍💋‍👩🏽" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.medium]) - } else if rawValue == "👩🏽‍❤️‍💋‍👩🏻" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.medium, .light]) - } else if rawValue == "👩🏽‍❤️‍💋‍👩🏼" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.medium, .mediumLight]) - } else if rawValue == "👩🏽‍❤️‍💋‍👩🏾" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.medium, .mediumDark]) - } else if rawValue == "👩🏽‍❤️‍💋‍👩🏿" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.medium, .dark]) - } else if rawValue == "👩🏾‍❤️‍💋‍👩🏾" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.mediumDark]) - } else if rawValue == "👩🏾‍❤️‍💋‍👩🏻" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.mediumDark, .light]) - } else if rawValue == "👩🏾‍❤️‍💋‍👩🏼" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.mediumDark, .mediumLight]) - } else if rawValue == "👩🏾‍❤️‍💋‍👩🏽" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.mediumDark, .medium]) - } else if rawValue == "👩🏾‍❤️‍💋‍👩🏿" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.mediumDark, .dark]) - } else if rawValue == "👩🏿‍❤️‍💋‍👩🏿" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.dark]) - } else if rawValue == "👩🏿‍❤️‍💋‍👩🏻" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.dark, .light]) - } else if rawValue == "👩🏿‍❤️‍💋‍👩🏼" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.dark, .mediumLight]) - } else if rawValue == "👩🏿‍❤️‍💋‍👩🏽" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.dark, .medium]) - } else if rawValue == "👩🏿‍❤️‍💋‍👩🏾" { - self.init(baseEmoji: .womanKissWoman, skinTones: [.dark, .mediumDark]) - } else if rawValue == "💑" { - self.init(baseEmoji: .personHeartPerson, skinTones: nil) - } else if rawValue == "💑🏻" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.light]) - } else if rawValue == "🧑🏻‍❤️‍🧑🏼" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.light, .mediumLight]) - } else if rawValue == "🧑🏻‍❤️‍🧑🏽" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.light, .medium]) - } else if rawValue == "🧑🏻‍❤️‍🧑🏾" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.light, .mediumDark]) - } else if rawValue == "🧑🏻‍❤️‍🧑🏿" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.light, .dark]) - } else if rawValue == "💑🏼" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.mediumLight]) - } else if rawValue == "🧑🏼‍❤️‍🧑🏻" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.mediumLight, .light]) - } else if rawValue == "🧑🏼‍❤️‍🧑🏽" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.mediumLight, .medium]) - } else if rawValue == "🧑🏼‍❤️‍🧑🏾" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.mediumLight, .mediumDark]) - } else if rawValue == "🧑🏼‍❤️‍🧑🏿" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.mediumLight, .dark]) - } else if rawValue == "💑🏽" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.medium]) - } else if rawValue == "🧑🏽‍❤️‍🧑🏻" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.medium, .light]) - } else if rawValue == "🧑🏽‍❤️‍🧑🏼" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.medium, .mediumLight]) - } else if rawValue == "🧑🏽‍❤️‍🧑🏾" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.medium, .mediumDark]) - } else if rawValue == "🧑🏽‍❤️‍🧑🏿" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.medium, .dark]) - } else if rawValue == "💑🏾" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.mediumDark]) - } else if rawValue == "🧑🏾‍❤️‍🧑🏻" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.mediumDark, .light]) - } else if rawValue == "🧑🏾‍❤️‍🧑🏼" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.mediumDark, .mediumLight]) - } else if rawValue == "🧑🏾‍❤️‍🧑🏽" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.mediumDark, .medium]) - } else if rawValue == "🧑🏾‍❤️‍🧑🏿" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.mediumDark, .dark]) - } else if rawValue == "💑🏿" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.dark]) - } else if rawValue == "🧑🏿‍❤️‍🧑🏻" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.dark, .light]) - } else if rawValue == "🧑🏿‍❤️‍🧑🏼" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.dark, .mediumLight]) - } else if rawValue == "🧑🏿‍❤️‍🧑🏽" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.dark, .medium]) - } else if rawValue == "🧑🏿‍❤️‍🧑🏾" { - self.init(baseEmoji: .personHeartPerson, skinTones: [.dark, .mediumDark]) - } else if rawValue == "👩‍❤️‍👨" { - self.init(baseEmoji: .womanHeartMan, skinTones: nil) - } else if rawValue == "👩🏻‍❤️‍👨🏻" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.light]) - } else if rawValue == "👩🏻‍❤️‍👨🏼" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.light, .mediumLight]) - } else if rawValue == "👩🏻‍❤️‍👨🏽" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.light, .medium]) - } else if rawValue == "👩🏻‍❤️‍👨🏾" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.light, .mediumDark]) - } else if rawValue == "👩🏻‍❤️‍👨🏿" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.light, .dark]) - } else if rawValue == "👩🏼‍❤️‍👨🏼" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.mediumLight]) - } else if rawValue == "👩🏼‍❤️‍👨🏻" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.mediumLight, .light]) - } else if rawValue == "👩🏼‍❤️‍👨🏽" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.mediumLight, .medium]) - } else if rawValue == "👩🏼‍❤️‍👨🏾" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.mediumLight, .mediumDark]) - } else if rawValue == "👩🏼‍❤️‍👨🏿" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.mediumLight, .dark]) - } else if rawValue == "👩🏽‍❤️‍👨🏽" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.medium]) - } else if rawValue == "👩🏽‍❤️‍👨🏻" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.medium, .light]) - } else if rawValue == "👩🏽‍❤️‍👨🏼" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.medium, .mediumLight]) - } else if rawValue == "👩🏽‍❤️‍👨🏾" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.medium, .mediumDark]) - } else if rawValue == "👩🏽‍❤️‍👨🏿" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.medium, .dark]) - } else if rawValue == "👩🏾‍❤️‍👨🏾" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.mediumDark]) - } else if rawValue == "👩🏾‍❤️‍👨🏻" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.mediumDark, .light]) - } else if rawValue == "👩🏾‍❤️‍👨🏼" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.mediumDark, .mediumLight]) - } else if rawValue == "👩🏾‍❤️‍👨🏽" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.mediumDark, .medium]) - } else if rawValue == "👩🏾‍❤️‍👨🏿" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.mediumDark, .dark]) - } else if rawValue == "👩🏿‍❤️‍👨🏿" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.dark]) - } else if rawValue == "👩🏿‍❤️‍👨🏻" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.dark, .light]) - } else if rawValue == "👩🏿‍❤️‍👨🏼" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.dark, .mediumLight]) - } else if rawValue == "👩🏿‍❤️‍👨🏽" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.dark, .medium]) - } else if rawValue == "👩🏿‍❤️‍👨🏾" { - self.init(baseEmoji: .womanHeartMan, skinTones: [.dark, .mediumDark]) - } else if rawValue == "👨‍❤️‍👨" { - self.init(baseEmoji: .manHeartMan, skinTones: nil) - } else if rawValue == "👨🏻‍❤️‍👨🏻" { - self.init(baseEmoji: .manHeartMan, skinTones: [.light]) - } else if rawValue == "👨🏻‍❤️‍👨🏼" { - self.init(baseEmoji: .manHeartMan, skinTones: [.light, .mediumLight]) - } else if rawValue == "👨🏻‍❤️‍👨🏽" { - self.init(baseEmoji: .manHeartMan, skinTones: [.light, .medium]) - } else if rawValue == "👨🏻‍❤️‍👨🏾" { - self.init(baseEmoji: .manHeartMan, skinTones: [.light, .mediumDark]) - } else if rawValue == "👨🏻‍❤️‍👨🏿" { - self.init(baseEmoji: .manHeartMan, skinTones: [.light, .dark]) - } else if rawValue == "👨🏼‍❤️‍👨🏼" { - self.init(baseEmoji: .manHeartMan, skinTones: [.mediumLight]) - } else if rawValue == "👨🏼‍❤️‍👨🏻" { - self.init(baseEmoji: .manHeartMan, skinTones: [.mediumLight, .light]) - } else if rawValue == "👨🏼‍❤️‍👨🏽" { - self.init(baseEmoji: .manHeartMan, skinTones: [.mediumLight, .medium]) - } else if rawValue == "👨🏼‍❤️‍👨🏾" { - self.init(baseEmoji: .manHeartMan, skinTones: [.mediumLight, .mediumDark]) - } else if rawValue == "👨🏼‍❤️‍👨🏿" { - self.init(baseEmoji: .manHeartMan, skinTones: [.mediumLight, .dark]) - } else if rawValue == "👨🏽‍❤️‍👨🏽" { - self.init(baseEmoji: .manHeartMan, skinTones: [.medium]) - } else if rawValue == "👨🏽‍❤️‍👨🏻" { - self.init(baseEmoji: .manHeartMan, skinTones: [.medium, .light]) - } else if rawValue == "👨🏽‍❤️‍👨🏼" { - self.init(baseEmoji: .manHeartMan, skinTones: [.medium, .mediumLight]) - } else if rawValue == "👨🏽‍❤️‍👨🏾" { - self.init(baseEmoji: .manHeartMan, skinTones: [.medium, .mediumDark]) - } else if rawValue == "👨🏽‍❤️‍👨🏿" { - self.init(baseEmoji: .manHeartMan, skinTones: [.medium, .dark]) - } else if rawValue == "👨🏾‍❤️‍👨🏾" { - self.init(baseEmoji: .manHeartMan, skinTones: [.mediumDark]) - } else if rawValue == "👨🏾‍❤️‍👨🏻" { - self.init(baseEmoji: .manHeartMan, skinTones: [.mediumDark, .light]) - } else if rawValue == "👨🏾‍❤️‍👨🏼" { - self.init(baseEmoji: .manHeartMan, skinTones: [.mediumDark, .mediumLight]) - } else if rawValue == "👨🏾‍❤️‍👨🏽" { - self.init(baseEmoji: .manHeartMan, skinTones: [.mediumDark, .medium]) - } else if rawValue == "👨🏾‍❤️‍👨🏿" { - self.init(baseEmoji: .manHeartMan, skinTones: [.mediumDark, .dark]) - } else if rawValue == "👨🏿‍❤️‍👨🏿" { - self.init(baseEmoji: .manHeartMan, skinTones: [.dark]) - } else if rawValue == "👨🏿‍❤️‍👨🏻" { - self.init(baseEmoji: .manHeartMan, skinTones: [.dark, .light]) - } else if rawValue == "👨🏿‍❤️‍👨🏼" { - self.init(baseEmoji: .manHeartMan, skinTones: [.dark, .mediumLight]) - } else if rawValue == "👨🏿‍❤️‍👨🏽" { - self.init(baseEmoji: .manHeartMan, skinTones: [.dark, .medium]) - } else if rawValue == "👨🏿‍❤️‍👨🏾" { - self.init(baseEmoji: .manHeartMan, skinTones: [.dark, .mediumDark]) - } else if rawValue == "👩‍❤️‍👩" { - self.init(baseEmoji: .womanHeartWoman, skinTones: nil) - } else if rawValue == "👩🏻‍❤️‍👩🏻" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.light]) - } else if rawValue == "👩🏻‍❤️‍👩🏼" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.light, .mediumLight]) - } else if rawValue == "👩🏻‍❤️‍👩🏽" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.light, .medium]) - } else if rawValue == "👩🏻‍❤️‍👩🏾" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.light, .mediumDark]) - } else if rawValue == "👩🏻‍❤️‍👩🏿" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.light, .dark]) - } else if rawValue == "👩🏼‍❤️‍👩🏼" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.mediumLight]) - } else if rawValue == "👩🏼‍❤️‍👩🏻" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.mediumLight, .light]) - } else if rawValue == "👩🏼‍❤️‍👩🏽" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.mediumLight, .medium]) - } else if rawValue == "👩🏼‍❤️‍👩🏾" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.mediumLight, .mediumDark]) - } else if rawValue == "👩🏼‍❤️‍👩🏿" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.mediumLight, .dark]) - } else if rawValue == "👩🏽‍❤️‍👩🏽" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.medium]) - } else if rawValue == "👩🏽‍❤️‍👩🏻" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.medium, .light]) - } else if rawValue == "👩🏽‍❤️‍👩🏼" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.medium, .mediumLight]) - } else if rawValue == "👩🏽‍❤️‍👩🏾" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.medium, .mediumDark]) - } else if rawValue == "👩🏽‍❤️‍👩🏿" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.medium, .dark]) - } else if rawValue == "👩🏾‍❤️‍👩🏾" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.mediumDark]) - } else if rawValue == "👩🏾‍❤️‍👩🏻" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.mediumDark, .light]) - } else if rawValue == "👩🏾‍❤️‍👩🏼" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.mediumDark, .mediumLight]) - } else if rawValue == "👩🏾‍❤️‍👩🏽" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.mediumDark, .medium]) - } else if rawValue == "👩🏾‍❤️‍👩🏿" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.mediumDark, .dark]) - } else if rawValue == "👩🏿‍❤️‍👩🏿" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.dark]) - } else if rawValue == "👩🏿‍❤️‍👩🏻" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.dark, .light]) - } else if rawValue == "👩🏿‍❤️‍👩🏼" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.dark, .mediumLight]) - } else if rawValue == "👩🏿‍❤️‍👩🏽" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.dark, .medium]) - } else if rawValue == "👩🏿‍❤️‍👩🏾" { - self.init(baseEmoji: .womanHeartWoman, skinTones: [.dark, .mediumDark]) - } else if rawValue == "👪" { - self.init(baseEmoji: .family, skinTones: nil) - } else if rawValue == "👨‍👩‍👦" { - self.init(baseEmoji: .manWomanBoy, skinTones: nil) - } else if rawValue == "👨‍👩‍👧" { - self.init(baseEmoji: .manWomanGirl, skinTones: nil) - } else if rawValue == "👨‍👩‍👧‍👦" { - self.init(baseEmoji: .manWomanGirlBoy, skinTones: nil) - } else if rawValue == "👨‍👩‍👦‍👦" { - self.init(baseEmoji: .manWomanBoyBoy, skinTones: nil) - } else if rawValue == "👨‍👩‍👧‍👧" { - self.init(baseEmoji: .manWomanGirlGirl, skinTones: nil) - } else if rawValue == "👨‍👨‍👦" { - self.init(baseEmoji: .manManBoy, skinTones: nil) - } else if rawValue == "👨‍👨‍👧" { - self.init(baseEmoji: .manManGirl, skinTones: nil) - } else if rawValue == "👨‍👨‍👧‍👦" { - self.init(baseEmoji: .manManGirlBoy, skinTones: nil) - } else if rawValue == "👨‍👨‍👦‍👦" { - self.init(baseEmoji: .manManBoyBoy, skinTones: nil) - } else if rawValue == "👨‍👨‍👧‍👧" { - self.init(baseEmoji: .manManGirlGirl, skinTones: nil) - } else if rawValue == "👩‍👩‍👦" { - self.init(baseEmoji: .womanWomanBoy, skinTones: nil) - } else if rawValue == "👩‍👩‍👧" { - self.init(baseEmoji: .womanWomanGirl, skinTones: nil) - } else if rawValue == "👩‍👩‍👧‍👦" { - self.init(baseEmoji: .womanWomanGirlBoy, skinTones: nil) - } else if rawValue == "👩‍👩‍👦‍👦" { - self.init(baseEmoji: .womanWomanBoyBoy, skinTones: nil) - } else if rawValue == "👩‍👩‍👧‍👧" { - self.init(baseEmoji: .womanWomanGirlGirl, skinTones: nil) - } else if rawValue == "👨‍👦" { - self.init(baseEmoji: .manBoy, skinTones: nil) - } else if rawValue == "👨‍👦‍👦" { - self.init(baseEmoji: .manBoyBoy, skinTones: nil) - } else if rawValue == "👨‍👧" { - self.init(baseEmoji: .manGirl, skinTones: nil) - } else if rawValue == "👨‍👧‍👦" { - self.init(baseEmoji: .manGirlBoy, skinTones: nil) - } else if rawValue == "👨‍👧‍👧" { - self.init(baseEmoji: .manGirlGirl, skinTones: nil) - } else if rawValue == "👩‍👦" { - self.init(baseEmoji: .womanBoy, skinTones: nil) - } else if rawValue == "👩‍👦‍👦" { - self.init(baseEmoji: .womanBoyBoy, skinTones: nil) - } else if rawValue == "👩‍👧" { - self.init(baseEmoji: .womanGirl, skinTones: nil) - } else if rawValue == "👩‍👧‍👦" { - self.init(baseEmoji: .womanGirlBoy, skinTones: nil) - } else if rawValue == "👩‍👧‍👧" { - self.init(baseEmoji: .womanGirlGirl, skinTones: nil) - } else if rawValue == "🗣️" { - self.init(baseEmoji: .speakingHeadInSilhouette, skinTones: nil) - } else if rawValue == "👤" { - self.init(baseEmoji: .bustInSilhouette, skinTones: nil) - } else if rawValue == "👥" { - self.init(baseEmoji: .bustsInSilhouette, skinTones: nil) - } else if rawValue == "🫂" { - self.init(baseEmoji: .peopleHugging, skinTones: nil) - } else if rawValue == "👣" { - self.init(baseEmoji: .footprints, skinTones: nil) - } else if rawValue == "🏻" { - self.init(baseEmoji: .skinTone2, skinTones: nil) - } else if rawValue == "🏼" { - self.init(baseEmoji: .skinTone3, skinTones: nil) - } else if rawValue == "🏽" { - self.init(baseEmoji: .skinTone4, skinTones: nil) - } else if rawValue == "🏾" { - self.init(baseEmoji: .skinTone5, skinTones: nil) - } else if rawValue == "🏿" { - self.init(baseEmoji: .skinTone6, skinTones: nil) - } else if rawValue == "🐵" { - self.init(baseEmoji: .monkeyFace, skinTones: nil) - } else if rawValue == "🐒" { - self.init(baseEmoji: .monkey, skinTones: nil) - } else if rawValue == "🦍" { - self.init(baseEmoji: .gorilla, skinTones: nil) - } else if rawValue == "🦧" { - self.init(baseEmoji: .orangutan, skinTones: nil) - } else if rawValue == "🐶" { - self.init(baseEmoji: .dog, skinTones: nil) - } else if rawValue == "🐕" { - self.init(baseEmoji: .dog2, skinTones: nil) - } else if rawValue == "🦮" { - self.init(baseEmoji: .guideDog, skinTones: nil) - } else if rawValue == "🐕‍🦺" { - self.init(baseEmoji: .serviceDog, skinTones: nil) - } else if rawValue == "🐩" { - self.init(baseEmoji: .poodle, skinTones: nil) - } else if rawValue == "🐺" { - self.init(baseEmoji: .wolf, skinTones: nil) - } else if rawValue == "🦊" { - self.init(baseEmoji: .foxFace, skinTones: nil) - } else if rawValue == "🦝" { - self.init(baseEmoji: .raccoon, skinTones: nil) - } else if rawValue == "🐱" { - self.init(baseEmoji: .cat, skinTones: nil) - } else if rawValue == "🐈" { - self.init(baseEmoji: .cat2, skinTones: nil) - } else if rawValue == "🐈‍⬛" { - self.init(baseEmoji: .blackCat, skinTones: nil) - } else if rawValue == "🦁" { - self.init(baseEmoji: .lionFace, skinTones: nil) - } else if rawValue == "🐯" { - self.init(baseEmoji: .tiger, skinTones: nil) - } else if rawValue == "🐅" { - self.init(baseEmoji: .tiger2, skinTones: nil) - } else if rawValue == "🐆" { - self.init(baseEmoji: .leopard, skinTones: nil) - } else if rawValue == "🐴" { - self.init(baseEmoji: .horse, skinTones: nil) - } else if rawValue == "🐎" { - self.init(baseEmoji: .racehorse, skinTones: nil) - } else if rawValue == "🦄" { - self.init(baseEmoji: .unicornFace, skinTones: nil) - } else if rawValue == "🦓" { - self.init(baseEmoji: .zebraFace, skinTones: nil) - } else if rawValue == "🦌" { - self.init(baseEmoji: .deer, skinTones: nil) - } else if rawValue == "🦬" { - self.init(baseEmoji: .bison, skinTones: nil) - } else if rawValue == "🐮" { - self.init(baseEmoji: .cow, skinTones: nil) - } else if rawValue == "🐂" { - self.init(baseEmoji: .ox, skinTones: nil) - } else if rawValue == "🐃" { - self.init(baseEmoji: .waterBuffalo, skinTones: nil) - } else if rawValue == "🐄" { - self.init(baseEmoji: .cow2, skinTones: nil) - } else if rawValue == "🐷" { - self.init(baseEmoji: .pig, skinTones: nil) - } else if rawValue == "🐖" { - self.init(baseEmoji: .pig2, skinTones: nil) - } else if rawValue == "🐗" { - self.init(baseEmoji: .boar, skinTones: nil) - } else if rawValue == "🐽" { - self.init(baseEmoji: .pigNose, skinTones: nil) - } else if rawValue == "🐏" { - self.init(baseEmoji: .ram, skinTones: nil) - } else if rawValue == "🐑" { - self.init(baseEmoji: .sheep, skinTones: nil) - } else if rawValue == "🐐" { - self.init(baseEmoji: .goat, skinTones: nil) - } else if rawValue == "🐪" { - self.init(baseEmoji: .dromedaryCamel, skinTones: nil) - } else if rawValue == "🐫" { - self.init(baseEmoji: .camel, skinTones: nil) - } else if rawValue == "🦙" { - self.init(baseEmoji: .llama, skinTones: nil) - } else if rawValue == "🦒" { - self.init(baseEmoji: .giraffeFace, skinTones: nil) - } else if rawValue == "🐘" { - self.init(baseEmoji: .elephant, skinTones: nil) - } else if rawValue == "🦣" { - self.init(baseEmoji: .mammoth, skinTones: nil) - } else if rawValue == "🦏" { - self.init(baseEmoji: .rhinoceros, skinTones: nil) - } else if rawValue == "🦛" { - self.init(baseEmoji: .hippopotamus, skinTones: nil) - } else if rawValue == "🐭" { - self.init(baseEmoji: .mouse, skinTones: nil) - } else if rawValue == "🐁" { - self.init(baseEmoji: .mouse2, skinTones: nil) - } else if rawValue == "🐀" { - self.init(baseEmoji: .rat, skinTones: nil) - } else if rawValue == "🐹" { - self.init(baseEmoji: .hamster, skinTones: nil) - } else if rawValue == "🐰" { - self.init(baseEmoji: .rabbit, skinTones: nil) - } else if rawValue == "🐇" { - self.init(baseEmoji: .rabbit2, skinTones: nil) - } else if rawValue == "🐿️" { - self.init(baseEmoji: .chipmunk, skinTones: nil) - } else if rawValue == "🦫" { - self.init(baseEmoji: .beaver, skinTones: nil) - } else if rawValue == "🦔" { - self.init(baseEmoji: .hedgehog, skinTones: nil) - } else if rawValue == "🦇" { - self.init(baseEmoji: .bat, skinTones: nil) - } else if rawValue == "🐻" { - self.init(baseEmoji: .bear, skinTones: nil) - } else if rawValue == "🐻‍❄️" { - self.init(baseEmoji: .polarBear, skinTones: nil) - } else if rawValue == "🐨" { - self.init(baseEmoji: .koala, skinTones: nil) - } else if rawValue == "🐼" { - self.init(baseEmoji: .pandaFace, skinTones: nil) - } else if rawValue == "🦥" { - self.init(baseEmoji: .sloth, skinTones: nil) - } else if rawValue == "🦦" { - self.init(baseEmoji: .otter, skinTones: nil) - } else if rawValue == "🦨" { - self.init(baseEmoji: .skunk, skinTones: nil) - } else if rawValue == "🦘" { - self.init(baseEmoji: .kangaroo, skinTones: nil) - } else if rawValue == "🦡" { - self.init(baseEmoji: .badger, skinTones: nil) - } else if rawValue == "🐾" { - self.init(baseEmoji: .feet, skinTones: nil) - } else if rawValue == "🦃" { - self.init(baseEmoji: .turkey, skinTones: nil) - } else if rawValue == "🐔" { - self.init(baseEmoji: .chicken, skinTones: nil) - } else if rawValue == "🐓" { - self.init(baseEmoji: .rooster, skinTones: nil) - } else if rawValue == "🐣" { - self.init(baseEmoji: .hatchingChick, skinTones: nil) - } else if rawValue == "🐤" { - self.init(baseEmoji: .babyChick, skinTones: nil) - } else if rawValue == "🐥" { - self.init(baseEmoji: .hatchedChick, skinTones: nil) - } else if rawValue == "🐦" { - self.init(baseEmoji: .bird, skinTones: nil) - } else if rawValue == "🐧" { - self.init(baseEmoji: .penguin, skinTones: nil) - } else if rawValue == "🕊️" { - self.init(baseEmoji: .doveOfPeace, skinTones: nil) - } else if rawValue == "🦅" { - self.init(baseEmoji: .eagle, skinTones: nil) - } else if rawValue == "🦆" { - self.init(baseEmoji: .duck, skinTones: nil) - } else if rawValue == "🦢" { - self.init(baseEmoji: .swan, skinTones: nil) - } else if rawValue == "🦉" { - self.init(baseEmoji: .owl, skinTones: nil) - } else if rawValue == "🦤" { - self.init(baseEmoji: .dodo, skinTones: nil) - } else if rawValue == "🪶" { - self.init(baseEmoji: .feather, skinTones: nil) - } else if rawValue == "🦩" { - self.init(baseEmoji: .flamingo, skinTones: nil) - } else if rawValue == "🦚" { - self.init(baseEmoji: .peacock, skinTones: nil) - } else if rawValue == "🦜" { - self.init(baseEmoji: .parrot, skinTones: nil) - } else if rawValue == "🐸" { - self.init(baseEmoji: .frog, skinTones: nil) - } else if rawValue == "🐊" { - self.init(baseEmoji: .crocodile, skinTones: nil) - } else if rawValue == "🐢" { - self.init(baseEmoji: .turtle, skinTones: nil) - } else if rawValue == "🦎" { - self.init(baseEmoji: .lizard, skinTones: nil) - } else if rawValue == "🐍" { - self.init(baseEmoji: .snake, skinTones: nil) - } else if rawValue == "🐲" { - self.init(baseEmoji: .dragonFace, skinTones: nil) - } else if rawValue == "🐉" { - self.init(baseEmoji: .dragon, skinTones: nil) - } else if rawValue == "🦕" { - self.init(baseEmoji: .sauropod, skinTones: nil) - } else if rawValue == "🦖" { - self.init(baseEmoji: .tRex, skinTones: nil) - } else if rawValue == "🐳" { - self.init(baseEmoji: .whale, skinTones: nil) - } else if rawValue == "🐋" { - self.init(baseEmoji: .whale2, skinTones: nil) - } else if rawValue == "🐬" { - self.init(baseEmoji: .dolphin, skinTones: nil) - } else if rawValue == "🦭" { - self.init(baseEmoji: .seal, skinTones: nil) - } else if rawValue == "🐟" { - self.init(baseEmoji: .fish, skinTones: nil) - } else if rawValue == "🐠" { - self.init(baseEmoji: .tropicalFish, skinTones: nil) - } else if rawValue == "🐡" { - self.init(baseEmoji: .blowfish, skinTones: nil) - } else if rawValue == "🦈" { - self.init(baseEmoji: .shark, skinTones: nil) - } else if rawValue == "🐙" { - self.init(baseEmoji: .octopus, skinTones: nil) - } else if rawValue == "🐚" { - self.init(baseEmoji: .shell, skinTones: nil) - } else if rawValue == "🪸" { - self.init(baseEmoji: .coral, skinTones: nil) - } else if rawValue == "🐌" { - self.init(baseEmoji: .snail, skinTones: nil) - } else if rawValue == "🦋" { - self.init(baseEmoji: .butterfly, skinTones: nil) - } else if rawValue == "🐛" { - self.init(baseEmoji: .bug, skinTones: nil) - } else if rawValue == "🐜" { - self.init(baseEmoji: .ant, skinTones: nil) - } else if rawValue == "🐝" { - self.init(baseEmoji: .bee, skinTones: nil) - } else if rawValue == "🪲" { - self.init(baseEmoji: .beetle, skinTones: nil) - } else if rawValue == "🐞" { - self.init(baseEmoji: .ladybug, skinTones: nil) - } else if rawValue == "🦗" { - self.init(baseEmoji: .cricket, skinTones: nil) - } else if rawValue == "🪳" { - self.init(baseEmoji: .cockroach, skinTones: nil) - } else if rawValue == "🕷️" { - self.init(baseEmoji: .spider, skinTones: nil) - } else if rawValue == "🕸️" { - self.init(baseEmoji: .spiderWeb, skinTones: nil) - } else if rawValue == "🦂" { - self.init(baseEmoji: .scorpion, skinTones: nil) - } else if rawValue == "🦟" { - self.init(baseEmoji: .mosquito, skinTones: nil) - } else if rawValue == "🪰" { - self.init(baseEmoji: .fly, skinTones: nil) - } else if rawValue == "🪱" { - self.init(baseEmoji: .worm, skinTones: nil) - } else if rawValue == "🦠" { - self.init(baseEmoji: .microbe, skinTones: nil) - } else if rawValue == "💐" { - self.init(baseEmoji: .bouquet, skinTones: nil) - } else if rawValue == "🌸" { - self.init(baseEmoji: .cherryBlossom, skinTones: nil) - } else if rawValue == "💮" { - self.init(baseEmoji: .whiteFlower, skinTones: nil) - } else if rawValue == "🪷" { - self.init(baseEmoji: .lotus, skinTones: nil) - } else if rawValue == "🏵️" { - self.init(baseEmoji: .rosette, skinTones: nil) - } else if rawValue == "🌹" { - self.init(baseEmoji: .rose, skinTones: nil) - } else if rawValue == "🥀" { - self.init(baseEmoji: .wiltedFlower, skinTones: nil) - } else if rawValue == "🌺" { - self.init(baseEmoji: .hibiscus, skinTones: nil) - } else if rawValue == "🌻" { - self.init(baseEmoji: .sunflower, skinTones: nil) - } else if rawValue == "🌼" { - self.init(baseEmoji: .blossom, skinTones: nil) - } else if rawValue == "🌷" { - self.init(baseEmoji: .tulip, skinTones: nil) - } else if rawValue == "🌱" { - self.init(baseEmoji: .seedling, skinTones: nil) - } else if rawValue == "🪴" { - self.init(baseEmoji: .pottedPlant, skinTones: nil) - } else if rawValue == "🌲" { - self.init(baseEmoji: .evergreenTree, skinTones: nil) - } else if rawValue == "🌳" { - self.init(baseEmoji: .deciduousTree, skinTones: nil) - } else if rawValue == "🌴" { - self.init(baseEmoji: .palmTree, skinTones: nil) - } else if rawValue == "🌵" { - self.init(baseEmoji: .cactus, skinTones: nil) - } else if rawValue == "🌾" { - self.init(baseEmoji: .earOfRice, skinTones: nil) - } else if rawValue == "🌿" { - self.init(baseEmoji: .herb, skinTones: nil) - } else if rawValue == "☘️" { - self.init(baseEmoji: .shamrock, skinTones: nil) - } else if rawValue == "🍀" { - self.init(baseEmoji: .fourLeafClover, skinTones: nil) - } else if rawValue == "🍁" { - self.init(baseEmoji: .mapleLeaf, skinTones: nil) - } else if rawValue == "🍂" { - self.init(baseEmoji: .fallenLeaf, skinTones: nil) - } else if rawValue == "🍃" { - self.init(baseEmoji: .leaves, skinTones: nil) - } else if rawValue == "🪹" { - self.init(baseEmoji: .emptyNest, skinTones: nil) - } else if rawValue == "🪺" { - self.init(baseEmoji: .nestWithEggs, skinTones: nil) - } else if rawValue == "🍇" { - self.init(baseEmoji: .grapes, skinTones: nil) - } else if rawValue == "🍈" { - self.init(baseEmoji: .melon, skinTones: nil) - } else if rawValue == "🍉" { - self.init(baseEmoji: .watermelon, skinTones: nil) - } else if rawValue == "🍊" { - self.init(baseEmoji: .tangerine, skinTones: nil) - } else if rawValue == "🍋" { - self.init(baseEmoji: .lemon, skinTones: nil) - } else if rawValue == "🍌" { - self.init(baseEmoji: .banana, skinTones: nil) - } else if rawValue == "🍍" { - self.init(baseEmoji: .pineapple, skinTones: nil) - } else if rawValue == "🥭" { - self.init(baseEmoji: .mango, skinTones: nil) - } else if rawValue == "🍎" { - self.init(baseEmoji: .apple, skinTones: nil) - } else if rawValue == "🍏" { - self.init(baseEmoji: .greenApple, skinTones: nil) - } else if rawValue == "🍐" { - self.init(baseEmoji: .pear, skinTones: nil) - } else if rawValue == "🍑" { - self.init(baseEmoji: .peach, skinTones: nil) - } else if rawValue == "🍒" { - self.init(baseEmoji: .cherries, skinTones: nil) - } else if rawValue == "🍓" { - self.init(baseEmoji: .strawberry, skinTones: nil) - } else if rawValue == "🫐" { - self.init(baseEmoji: .blueberries, skinTones: nil) - } else if rawValue == "🥝" { - self.init(baseEmoji: .kiwifruit, skinTones: nil) - } else if rawValue == "🍅" { - self.init(baseEmoji: .tomato, skinTones: nil) - } else if rawValue == "🫒" { - self.init(baseEmoji: .olive, skinTones: nil) - } else if rawValue == "🥥" { - self.init(baseEmoji: .coconut, skinTones: nil) - } else if rawValue == "🥑" { - self.init(baseEmoji: .avocado, skinTones: nil) - } else if rawValue == "🍆" { - self.init(baseEmoji: .eggplant, skinTones: nil) - } else if rawValue == "🥔" { - self.init(baseEmoji: .potato, skinTones: nil) - } else if rawValue == "🥕" { - self.init(baseEmoji: .carrot, skinTones: nil) - } else if rawValue == "🌽" { - self.init(baseEmoji: .corn, skinTones: nil) - } else if rawValue == "🌶️" { - self.init(baseEmoji: .hotPepper, skinTones: nil) - } else if rawValue == "🫑" { - self.init(baseEmoji: .bellPepper, skinTones: nil) - } else if rawValue == "🥒" { - self.init(baseEmoji: .cucumber, skinTones: nil) - } else if rawValue == "🥬" { - self.init(baseEmoji: .leafyGreen, skinTones: nil) - } else if rawValue == "🥦" { - self.init(baseEmoji: .broccoli, skinTones: nil) - } else if rawValue == "🧄" { - self.init(baseEmoji: .garlic, skinTones: nil) - } else if rawValue == "🧅" { - self.init(baseEmoji: .onion, skinTones: nil) - } else if rawValue == "🍄" { - self.init(baseEmoji: .mushroom, skinTones: nil) - } else if rawValue == "🥜" { - self.init(baseEmoji: .peanuts, skinTones: nil) - } else if rawValue == "🫘" { - self.init(baseEmoji: .beans, skinTones: nil) - } else if rawValue == "🌰" { - self.init(baseEmoji: .chestnut, skinTones: nil) - } else if rawValue == "🍞" { - self.init(baseEmoji: .bread, skinTones: nil) - } else if rawValue == "🥐" { - self.init(baseEmoji: .croissant, skinTones: nil) - } else if rawValue == "🥖" { - self.init(baseEmoji: .baguetteBread, skinTones: nil) - } else if rawValue == "🫓" { - self.init(baseEmoji: .flatbread, skinTones: nil) - } else if rawValue == "🥨" { - self.init(baseEmoji: .pretzel, skinTones: nil) - } else if rawValue == "🥯" { - self.init(baseEmoji: .bagel, skinTones: nil) - } else if rawValue == "🥞" { - self.init(baseEmoji: .pancakes, skinTones: nil) - } else if rawValue == "🧇" { - self.init(baseEmoji: .waffle, skinTones: nil) - } else if rawValue == "🧀" { - self.init(baseEmoji: .cheeseWedge, skinTones: nil) - } else if rawValue == "🍖" { - self.init(baseEmoji: .meatOnBone, skinTones: nil) - } else if rawValue == "🍗" { - self.init(baseEmoji: .poultryLeg, skinTones: nil) - } else if rawValue == "🥩" { - self.init(baseEmoji: .cutOfMeat, skinTones: nil) - } else if rawValue == "🥓" { - self.init(baseEmoji: .bacon, skinTones: nil) - } else if rawValue == "🍔" { - self.init(baseEmoji: .hamburger, skinTones: nil) - } else if rawValue == "🍟" { - self.init(baseEmoji: .fries, skinTones: nil) - } else if rawValue == "🍕" { - self.init(baseEmoji: .pizza, skinTones: nil) - } else if rawValue == "🌭" { - self.init(baseEmoji: .hotdog, skinTones: nil) - } else if rawValue == "🥪" { - self.init(baseEmoji: .sandwich, skinTones: nil) - } else if rawValue == "🌮" { - self.init(baseEmoji: .taco, skinTones: nil) - } else if rawValue == "🌯" { - self.init(baseEmoji: .burrito, skinTones: nil) - } else if rawValue == "🫔" { - self.init(baseEmoji: .tamale, skinTones: nil) - } else if rawValue == "🥙" { - self.init(baseEmoji: .stuffedFlatbread, skinTones: nil) - } else if rawValue == "🧆" { - self.init(baseEmoji: .falafel, skinTones: nil) - } else if rawValue == "🥚" { - self.init(baseEmoji: .egg, skinTones: nil) - } else if rawValue == "🍳" { - self.init(baseEmoji: .friedEgg, skinTones: nil) - } else if rawValue == "🥘" { - self.init(baseEmoji: .shallowPanOfFood, skinTones: nil) - } else if rawValue == "🍲" { - self.init(baseEmoji: .stew, skinTones: nil) - } else if rawValue == "🫕" { - self.init(baseEmoji: .fondue, skinTones: nil) - } else if rawValue == "🥣" { - self.init(baseEmoji: .bowlWithSpoon, skinTones: nil) - } else if rawValue == "🥗" { - self.init(baseEmoji: .greenSalad, skinTones: nil) - } else if rawValue == "🍿" { - self.init(baseEmoji: .popcorn, skinTones: nil) - } else if rawValue == "🧈" { - self.init(baseEmoji: .butter, skinTones: nil) - } else if rawValue == "🧂" { - self.init(baseEmoji: .salt, skinTones: nil) - } else if rawValue == "🥫" { - self.init(baseEmoji: .cannedFood, skinTones: nil) - } else if rawValue == "🍱" { - self.init(baseEmoji: .bento, skinTones: nil) - } else if rawValue == "🍘" { - self.init(baseEmoji: .riceCracker, skinTones: nil) - } else if rawValue == "🍙" { - self.init(baseEmoji: .riceBall, skinTones: nil) - } else if rawValue == "🍚" { - self.init(baseEmoji: .rice, skinTones: nil) - } else if rawValue == "🍛" { - self.init(baseEmoji: .curry, skinTones: nil) - } else if rawValue == "🍜" { - self.init(baseEmoji: .ramen, skinTones: nil) - } else if rawValue == "🍝" { - self.init(baseEmoji: .spaghetti, skinTones: nil) - } else if rawValue == "🍠" { - self.init(baseEmoji: .sweetPotato, skinTones: nil) - } else if rawValue == "🍢" { - self.init(baseEmoji: .oden, skinTones: nil) - } else if rawValue == "🍣" { - self.init(baseEmoji: .sushi, skinTones: nil) - } else if rawValue == "🍤" { - self.init(baseEmoji: .friedShrimp, skinTones: nil) - } else if rawValue == "🍥" { - self.init(baseEmoji: .fishCake, skinTones: nil) - } else if rawValue == "🥮" { - self.init(baseEmoji: .moonCake, skinTones: nil) - } else if rawValue == "🍡" { - self.init(baseEmoji: .dango, skinTones: nil) - } else if rawValue == "🥟" { - self.init(baseEmoji: .dumpling, skinTones: nil) - } else if rawValue == "🥠" { - self.init(baseEmoji: .fortuneCookie, skinTones: nil) - } else if rawValue == "🥡" { - self.init(baseEmoji: .takeoutBox, skinTones: nil) - } else if rawValue == "🦀" { - self.init(baseEmoji: .crab, skinTones: nil) - } else if rawValue == "🦞" { - self.init(baseEmoji: .lobster, skinTones: nil) - } else if rawValue == "🦐" { - self.init(baseEmoji: .shrimp, skinTones: nil) - } else if rawValue == "🦑" { - self.init(baseEmoji: .squid, skinTones: nil) - } else if rawValue == "🦪" { - self.init(baseEmoji: .oyster, skinTones: nil) - } else if rawValue == "🍦" { - self.init(baseEmoji: .icecream, skinTones: nil) - } else if rawValue == "🍧" { - self.init(baseEmoji: .shavedIce, skinTones: nil) - } else if rawValue == "🍨" { - self.init(baseEmoji: .iceCream, skinTones: nil) - } else if rawValue == "🍩" { - self.init(baseEmoji: .doughnut, skinTones: nil) - } else if rawValue == "🍪" { - self.init(baseEmoji: .cookie, skinTones: nil) - } else if rawValue == "🎂" { - self.init(baseEmoji: .birthday, skinTones: nil) - } else if rawValue == "🍰" { - self.init(baseEmoji: .cake, skinTones: nil) - } else if rawValue == "🧁" { - self.init(baseEmoji: .cupcake, skinTones: nil) - } else if rawValue == "🥧" { - self.init(baseEmoji: .pie, skinTones: nil) - } else if rawValue == "🍫" { - self.init(baseEmoji: .chocolateBar, skinTones: nil) - } else if rawValue == "🍬" { - self.init(baseEmoji: .candy, skinTones: nil) - } else if rawValue == "🍭" { - self.init(baseEmoji: .lollipop, skinTones: nil) - } else if rawValue == "🍮" { - self.init(baseEmoji: .custard, skinTones: nil) - } else if rawValue == "🍯" { - self.init(baseEmoji: .honeyPot, skinTones: nil) - } else if rawValue == "🍼" { - self.init(baseEmoji: .babyBottle, skinTones: nil) - } else if rawValue == "🥛" { - self.init(baseEmoji: .glassOfMilk, skinTones: nil) - } else if rawValue == "☕" { - self.init(baseEmoji: .coffee, skinTones: nil) - } else if rawValue == "🫖" { - self.init(baseEmoji: .teapot, skinTones: nil) - } else if rawValue == "🍵" { - self.init(baseEmoji: .tea, skinTones: nil) - } else if rawValue == "🍶" { - self.init(baseEmoji: .sake, skinTones: nil) - } else if rawValue == "🍾" { - self.init(baseEmoji: .champagne, skinTones: nil) - } else if rawValue == "🍷" { - self.init(baseEmoji: .wineGlass, skinTones: nil) - } else if rawValue == "🍸" { - self.init(baseEmoji: .cocktail, skinTones: nil) - } else if rawValue == "🍹" { - self.init(baseEmoji: .tropicalDrink, skinTones: nil) - } else if rawValue == "🍺" { - self.init(baseEmoji: .beer, skinTones: nil) - } else if rawValue == "🍻" { - self.init(baseEmoji: .beers, skinTones: nil) - } else if rawValue == "🥂" { - self.init(baseEmoji: .clinkingGlasses, skinTones: nil) - } else if rawValue == "🥃" { - self.init(baseEmoji: .tumblerGlass, skinTones: nil) - } else if rawValue == "🫗" { - self.init(baseEmoji: .pouringLiquid, skinTones: nil) - } else if rawValue == "🥤" { - self.init(baseEmoji: .cupWithStraw, skinTones: nil) - } else if rawValue == "🧋" { - self.init(baseEmoji: .bubbleTea, skinTones: nil) - } else if rawValue == "🧃" { - self.init(baseEmoji: .beverageBox, skinTones: nil) - } else if rawValue == "🧉" { - self.init(baseEmoji: .mateDrink, skinTones: nil) - } else if rawValue == "🧊" { - self.init(baseEmoji: .iceCube, skinTones: nil) - } else if rawValue == "🥢" { - self.init(baseEmoji: .chopsticks, skinTones: nil) - } else if rawValue == "🍽️" { - self.init(baseEmoji: .knifeForkPlate, skinTones: nil) - } else if rawValue == "🍴" { - self.init(baseEmoji: .forkAndKnife, skinTones: nil) - } else if rawValue == "🥄" { - self.init(baseEmoji: .spoon, skinTones: nil) - } else if rawValue == "🔪" { - self.init(baseEmoji: .hocho, skinTones: nil) - } else if rawValue == "🫙" { - self.init(baseEmoji: .jar, skinTones: nil) - } else if rawValue == "🏺" { - self.init(baseEmoji: .amphora, skinTones: nil) - } else if rawValue == "🌍" { - self.init(baseEmoji: .earthAfrica, skinTones: nil) - } else if rawValue == "🌎" { - self.init(baseEmoji: .earthAmericas, skinTones: nil) - } else if rawValue == "🌏" { - self.init(baseEmoji: .earthAsia, skinTones: nil) - } else if rawValue == "🌐" { - self.init(baseEmoji: .globeWithMeridians, skinTones: nil) - } else if rawValue == "🗺️" { - self.init(baseEmoji: .worldMap, skinTones: nil) - } else if rawValue == "🗾" { - self.init(baseEmoji: .japan, skinTones: nil) - } else if rawValue == "🧭" { - self.init(baseEmoji: .compass, skinTones: nil) - } else if rawValue == "🏔️" { - self.init(baseEmoji: .snowCappedMountain, skinTones: nil) - } else if rawValue == "⛰️" { - self.init(baseEmoji: .mountain, skinTones: nil) - } else if rawValue == "🌋" { - self.init(baseEmoji: .volcano, skinTones: nil) - } else if rawValue == "🗻" { - self.init(baseEmoji: .mountFuji, skinTones: nil) - } else if rawValue == "🏕️" { - self.init(baseEmoji: .camping, skinTones: nil) - } else if rawValue == "🏖️" { - self.init(baseEmoji: .beachWithUmbrella, skinTones: nil) - } else if rawValue == "🏜️" { - self.init(baseEmoji: .desert, skinTones: nil) - } else if rawValue == "🏝️" { - self.init(baseEmoji: .desertIsland, skinTones: nil) - } else if rawValue == "🏞️" { - self.init(baseEmoji: .nationalPark, skinTones: nil) - } else if rawValue == "🏟️" { - self.init(baseEmoji: .stadium, skinTones: nil) - } else if rawValue == "🏛️" { - self.init(baseEmoji: .classicalBuilding, skinTones: nil) - } else if rawValue == "🏗️" { - self.init(baseEmoji: .buildingConstruction, skinTones: nil) - } else if rawValue == "🧱" { - self.init(baseEmoji: .bricks, skinTones: nil) - } else if rawValue == "🪨" { - self.init(baseEmoji: .rock, skinTones: nil) - } else if rawValue == "🪵" { - self.init(baseEmoji: .wood, skinTones: nil) - } else if rawValue == "🛖" { - self.init(baseEmoji: .hut, skinTones: nil) - } else if rawValue == "🏘️" { - self.init(baseEmoji: .houseBuildings, skinTones: nil) - } else if rawValue == "🏚️" { - self.init(baseEmoji: .derelictHouseBuilding, skinTones: nil) - } else if rawValue == "🏠" { - self.init(baseEmoji: .house, skinTones: nil) - } else if rawValue == "🏡" { - self.init(baseEmoji: .houseWithGarden, skinTones: nil) - } else if rawValue == "🏢" { - self.init(baseEmoji: .office, skinTones: nil) - } else if rawValue == "🏣" { - self.init(baseEmoji: .postOffice, skinTones: nil) - } else if rawValue == "🏤" { - self.init(baseEmoji: .europeanPostOffice, skinTones: nil) - } else if rawValue == "🏥" { - self.init(baseEmoji: .hospital, skinTones: nil) - } else if rawValue == "🏦" { - self.init(baseEmoji: .bank, skinTones: nil) - } else if rawValue == "🏨" { - self.init(baseEmoji: .hotel, skinTones: nil) - } else if rawValue == "🏩" { - self.init(baseEmoji: .loveHotel, skinTones: nil) - } else if rawValue == "🏪" { - self.init(baseEmoji: .convenienceStore, skinTones: nil) - } else if rawValue == "🏫" { - self.init(baseEmoji: .school, skinTones: nil) - } else if rawValue == "🏬" { - self.init(baseEmoji: .departmentStore, skinTones: nil) - } else if rawValue == "🏭" { - self.init(baseEmoji: .factory, skinTones: nil) - } else if rawValue == "🏯" { - self.init(baseEmoji: .japaneseCastle, skinTones: nil) - } else if rawValue == "🏰" { - self.init(baseEmoji: .europeanCastle, skinTones: nil) - } else if rawValue == "💒" { - self.init(baseEmoji: .wedding, skinTones: nil) - } else if rawValue == "🗼" { - self.init(baseEmoji: .tokyoTower, skinTones: nil) - } else if rawValue == "🗽" { - self.init(baseEmoji: .statueOfLiberty, skinTones: nil) - } else if rawValue == "⛪" { - self.init(baseEmoji: .church, skinTones: nil) - } else if rawValue == "🕌" { - self.init(baseEmoji: .mosque, skinTones: nil) - } else if rawValue == "🛕" { - self.init(baseEmoji: .hinduTemple, skinTones: nil) - } else if rawValue == "🕍" { - self.init(baseEmoji: .synagogue, skinTones: nil) - } else if rawValue == "⛩️" { - self.init(baseEmoji: .shintoShrine, skinTones: nil) - } else if rawValue == "🕋" { - self.init(baseEmoji: .kaaba, skinTones: nil) - } else if rawValue == "⛲" { - self.init(baseEmoji: .fountain, skinTones: nil) - } else if rawValue == "⛺" { - self.init(baseEmoji: .tent, skinTones: nil) - } else if rawValue == "🌁" { - self.init(baseEmoji: .foggy, skinTones: nil) - } else if rawValue == "🌃" { - self.init(baseEmoji: .nightWithStars, skinTones: nil) - } else if rawValue == "🏙️" { - self.init(baseEmoji: .cityscape, skinTones: nil) - } else if rawValue == "🌄" { - self.init(baseEmoji: .sunriseOverMountains, skinTones: nil) - } else if rawValue == "🌅" { - self.init(baseEmoji: .sunrise, skinTones: nil) - } else if rawValue == "🌆" { - self.init(baseEmoji: .citySunset, skinTones: nil) - } else if rawValue == "🌇" { - self.init(baseEmoji: .citySunrise, skinTones: nil) - } else if rawValue == "🌉" { - self.init(baseEmoji: .bridgeAtNight, skinTones: nil) - } else if rawValue == "♨️" { - self.init(baseEmoji: .hotsprings, skinTones: nil) - } else if rawValue == "🎠" { - self.init(baseEmoji: .carouselHorse, skinTones: nil) - } else if rawValue == "🛝" { - self.init(baseEmoji: .playgroundSlide, skinTones: nil) - } else if rawValue == "🎡" { - self.init(baseEmoji: .ferrisWheel, skinTones: nil) - } else if rawValue == "🎢" { - self.init(baseEmoji: .rollerCoaster, skinTones: nil) - } else if rawValue == "💈" { - self.init(baseEmoji: .barber, skinTones: nil) - } else if rawValue == "🎪" { - self.init(baseEmoji: .circusTent, skinTones: nil) - } else if rawValue == "🚂" { - self.init(baseEmoji: .steamLocomotive, skinTones: nil) - } else if rawValue == "🚃" { - self.init(baseEmoji: .railwayCar, skinTones: nil) - } else if rawValue == "🚄" { - self.init(baseEmoji: .bullettrainSide, skinTones: nil) - } else if rawValue == "🚅" { - self.init(baseEmoji: .bullettrainFront, skinTones: nil) - } else if rawValue == "🚆" { - self.init(baseEmoji: .train2, skinTones: nil) - } else if rawValue == "🚇" { - self.init(baseEmoji: .metro, skinTones: nil) - } else if rawValue == "🚈" { - self.init(baseEmoji: .lightRail, skinTones: nil) - } else if rawValue == "🚉" { - self.init(baseEmoji: .station, skinTones: nil) - } else if rawValue == "🚊" { - self.init(baseEmoji: .tram, skinTones: nil) - } else if rawValue == "🚝" { - self.init(baseEmoji: .monorail, skinTones: nil) - } else if rawValue == "🚞" { - self.init(baseEmoji: .mountainRailway, skinTones: nil) - } else if rawValue == "🚋" { - self.init(baseEmoji: .train, skinTones: nil) - } else if rawValue == "🚌" { - self.init(baseEmoji: .bus, skinTones: nil) - } else if rawValue == "🚍" { - self.init(baseEmoji: .oncomingBus, skinTones: nil) - } else if rawValue == "🚎" { - self.init(baseEmoji: .trolleybus, skinTones: nil) - } else if rawValue == "🚐" { - self.init(baseEmoji: .minibus, skinTones: nil) - } else if rawValue == "🚑" { - self.init(baseEmoji: .ambulance, skinTones: nil) - } else if rawValue == "🚒" { - self.init(baseEmoji: .fireEngine, skinTones: nil) - } else if rawValue == "🚓" { - self.init(baseEmoji: .policeCar, skinTones: nil) - } else if rawValue == "🚔" { - self.init(baseEmoji: .oncomingPoliceCar, skinTones: nil) - } else if rawValue == "🚕" { - self.init(baseEmoji: .taxi, skinTones: nil) - } else if rawValue == "🚖" { - self.init(baseEmoji: .oncomingTaxi, skinTones: nil) - } else if rawValue == "🚗" { - self.init(baseEmoji: .car, skinTones: nil) - } else if rawValue == "🚘" { - self.init(baseEmoji: .oncomingAutomobile, skinTones: nil) - } else if rawValue == "🚙" { - self.init(baseEmoji: .blueCar, skinTones: nil) - } else if rawValue == "🛻" { - self.init(baseEmoji: .pickupTruck, skinTones: nil) - } else if rawValue == "🚚" { - self.init(baseEmoji: .truck, skinTones: nil) - } else if rawValue == "🚛" { - self.init(baseEmoji: .articulatedLorry, skinTones: nil) - } else if rawValue == "🚜" { - self.init(baseEmoji: .tractor, skinTones: nil) - } else if rawValue == "🏎️" { - self.init(baseEmoji: .racingCar, skinTones: nil) - } else if rawValue == "🏍️" { - self.init(baseEmoji: .racingMotorcycle, skinTones: nil) - } else if rawValue == "🛵" { - self.init(baseEmoji: .motorScooter, skinTones: nil) - } else if rawValue == "🦽" { - self.init(baseEmoji: .manualWheelchair, skinTones: nil) - } else if rawValue == "🦼" { - self.init(baseEmoji: .motorizedWheelchair, skinTones: nil) - } else if rawValue == "🛺" { - self.init(baseEmoji: .autoRickshaw, skinTones: nil) - } else if rawValue == "🚲" { - self.init(baseEmoji: .bike, skinTones: nil) - } else if rawValue == "🛴" { - self.init(baseEmoji: .scooter, skinTones: nil) - } else if rawValue == "🛹" { - self.init(baseEmoji: .skateboard, skinTones: nil) - } else if rawValue == "🛼" { - self.init(baseEmoji: .rollerSkate, skinTones: nil) - } else if rawValue == "🚏" { - self.init(baseEmoji: .busstop, skinTones: nil) - } else if rawValue == "🛣️" { - self.init(baseEmoji: .motorway, skinTones: nil) - } else if rawValue == "🛤️" { - self.init(baseEmoji: .railwayTrack, skinTones: nil) - } else if rawValue == "🛢️" { - self.init(baseEmoji: .oilDrum, skinTones: nil) - } else if rawValue == "⛽" { - self.init(baseEmoji: .fuelpump, skinTones: nil) - } else if rawValue == "🛞" { - self.init(baseEmoji: .wheel, skinTones: nil) - } else if rawValue == "🚨" { - self.init(baseEmoji: .rotatingLight, skinTones: nil) - } else if rawValue == "🚥" { - self.init(baseEmoji: .trafficLight, skinTones: nil) - } else if rawValue == "🚦" { - self.init(baseEmoji: .verticalTrafficLight, skinTones: nil) - } else if rawValue == "🛑" { - self.init(baseEmoji: .octagonalSign, skinTones: nil) - } else if rawValue == "🚧" { - self.init(baseEmoji: .construction, skinTones: nil) - } else if rawValue == "⚓" { - self.init(baseEmoji: .anchor, skinTones: nil) - } else if rawValue == "🛟" { - self.init(baseEmoji: .ringBuoy, skinTones: nil) - } else if rawValue == "⛵" { - self.init(baseEmoji: .boat, skinTones: nil) - } else if rawValue == "🛶" { - self.init(baseEmoji: .canoe, skinTones: nil) - } else if rawValue == "🚤" { - self.init(baseEmoji: .speedboat, skinTones: nil) - } else if rawValue == "🛳️" { - self.init(baseEmoji: .passengerShip, skinTones: nil) - } else if rawValue == "⛴️" { - self.init(baseEmoji: .ferry, skinTones: nil) - } else if rawValue == "🛥️" { - self.init(baseEmoji: .motorBoat, skinTones: nil) - } else if rawValue == "🚢" { - self.init(baseEmoji: .ship, skinTones: nil) - } else if rawValue == "✈️" { - self.init(baseEmoji: .airplane, skinTones: nil) - } else if rawValue == "🛩️" { - self.init(baseEmoji: .smallAirplane, skinTones: nil) - } else if rawValue == "🛫" { - self.init(baseEmoji: .airplaneDeparture, skinTones: nil) - } else if rawValue == "🛬" { - self.init(baseEmoji: .airplaneArriving, skinTones: nil) - } else if rawValue == "🪂" { - self.init(baseEmoji: .parachute, skinTones: nil) - } else if rawValue == "💺" { - self.init(baseEmoji: .seat, skinTones: nil) - } else if rawValue == "🚁" { - self.init(baseEmoji: .helicopter, skinTones: nil) - } else if rawValue == "🚟" { - self.init(baseEmoji: .suspensionRailway, skinTones: nil) - } else if rawValue == "🚠" { - self.init(baseEmoji: .mountainCableway, skinTones: nil) - } else if rawValue == "🚡" { - self.init(baseEmoji: .aerialTramway, skinTones: nil) - } else if rawValue == "🛰️" { - self.init(baseEmoji: .satellite, skinTones: nil) - } else if rawValue == "🚀" { - self.init(baseEmoji: .rocket, skinTones: nil) - } else if rawValue == "🛸" { - self.init(baseEmoji: .flyingSaucer, skinTones: nil) - } else if rawValue == "🛎️" { - self.init(baseEmoji: .bellhopBell, skinTones: nil) - } else if rawValue == "🧳" { - self.init(baseEmoji: .luggage, skinTones: nil) - } else if rawValue == "⌛" { - self.init(baseEmoji: .hourglass, skinTones: nil) - } else if rawValue == "⏳" { - self.init(baseEmoji: .hourglassFlowingSand, skinTones: nil) - } else if rawValue == "⌚" { - self.init(baseEmoji: .watch, skinTones: nil) - } else if rawValue == "⏰" { - self.init(baseEmoji: .alarmClock, skinTones: nil) - } else if rawValue == "⏱️" { - self.init(baseEmoji: .stopwatch, skinTones: nil) - } else if rawValue == "⏲️" { - self.init(baseEmoji: .timerClock, skinTones: nil) - } else if rawValue == "🕰️" { - self.init(baseEmoji: .mantelpieceClock, skinTones: nil) - } else if rawValue == "🕛" { - self.init(baseEmoji: .clock12, skinTones: nil) - } else if rawValue == "🕧" { - self.init(baseEmoji: .clock1230, skinTones: nil) - } else if rawValue == "🕐" { - self.init(baseEmoji: .clock1, skinTones: nil) - } else if rawValue == "🕜" { - self.init(baseEmoji: .clock130, skinTones: nil) - } else if rawValue == "🕑" { - self.init(baseEmoji: .clock2, skinTones: nil) - } else if rawValue == "🕝" { - self.init(baseEmoji: .clock230, skinTones: nil) - } else if rawValue == "🕒" { - self.init(baseEmoji: .clock3, skinTones: nil) - } else if rawValue == "🕞" { - self.init(baseEmoji: .clock330, skinTones: nil) - } else if rawValue == "🕓" { - self.init(baseEmoji: .clock4, skinTones: nil) - } else if rawValue == "🕟" { - self.init(baseEmoji: .clock430, skinTones: nil) - } else if rawValue == "🕔" { - self.init(baseEmoji: .clock5, skinTones: nil) - } else if rawValue == "🕠" { - self.init(baseEmoji: .clock530, skinTones: nil) - } else if rawValue == "🕕" { - self.init(baseEmoji: .clock6, skinTones: nil) - } else if rawValue == "🕡" { - self.init(baseEmoji: .clock630, skinTones: nil) - } else if rawValue == "🕖" { - self.init(baseEmoji: .clock7, skinTones: nil) - } else if rawValue == "🕢" { - self.init(baseEmoji: .clock730, skinTones: nil) - } else if rawValue == "🕗" { - self.init(baseEmoji: .clock8, skinTones: nil) - } else if rawValue == "🕣" { - self.init(baseEmoji: .clock830, skinTones: nil) - } else if rawValue == "🕘" { - self.init(baseEmoji: .clock9, skinTones: nil) - } else if rawValue == "🕤" { - self.init(baseEmoji: .clock930, skinTones: nil) - } else if rawValue == "🕙" { - self.init(baseEmoji: .clock10, skinTones: nil) - } else if rawValue == "🕥" { - self.init(baseEmoji: .clock1030, skinTones: nil) - } else if rawValue == "🕚" { - self.init(baseEmoji: .clock11, skinTones: nil) - } else if rawValue == "🕦" { - self.init(baseEmoji: .clock1130, skinTones: nil) - } else if rawValue == "🌑" { - self.init(baseEmoji: .newMoon, skinTones: nil) - } else if rawValue == "🌒" { - self.init(baseEmoji: .waxingCrescentMoon, skinTones: nil) - } else if rawValue == "🌓" { - self.init(baseEmoji: .firstQuarterMoon, skinTones: nil) - } else if rawValue == "🌔" { - self.init(baseEmoji: .moon, skinTones: nil) - } else if rawValue == "🌕" { - self.init(baseEmoji: .fullMoon, skinTones: nil) - } else if rawValue == "🌖" { - self.init(baseEmoji: .waningGibbousMoon, skinTones: nil) - } else if rawValue == "🌗" { - self.init(baseEmoji: .lastQuarterMoon, skinTones: nil) - } else if rawValue == "🌘" { - self.init(baseEmoji: .waningCrescentMoon, skinTones: nil) - } else if rawValue == "🌙" { - self.init(baseEmoji: .crescentMoon, skinTones: nil) - } else if rawValue == "🌚" { - self.init(baseEmoji: .newMoonWithFace, skinTones: nil) - } else if rawValue == "🌛" { - self.init(baseEmoji: .firstQuarterMoonWithFace, skinTones: nil) - } else if rawValue == "🌜" { - self.init(baseEmoji: .lastQuarterMoonWithFace, skinTones: nil) - } else if rawValue == "🌡️" { - self.init(baseEmoji: .thermometer, skinTones: nil) - } else if rawValue == "☀️" { - self.init(baseEmoji: .sunny, skinTones: nil) - } else if rawValue == "🌝" { - self.init(baseEmoji: .fullMoonWithFace, skinTones: nil) - } else if rawValue == "🌞" { - self.init(baseEmoji: .sunWithFace, skinTones: nil) - } else if rawValue == "🪐" { - self.init(baseEmoji: .ringedPlanet, skinTones: nil) - } else if rawValue == "⭐" { - self.init(baseEmoji: .star, skinTones: nil) - } else if rawValue == "🌟" { - self.init(baseEmoji: .star2, skinTones: nil) - } else if rawValue == "🌠" { - self.init(baseEmoji: .stars, skinTones: nil) - } else if rawValue == "🌌" { - self.init(baseEmoji: .milkyWay, skinTones: nil) - } else if rawValue == "☁️" { - self.init(baseEmoji: .cloud, skinTones: nil) - } else if rawValue == "⛅" { - self.init(baseEmoji: .partlySunny, skinTones: nil) - } else if rawValue == "⛈️" { - self.init(baseEmoji: .thunderCloudAndRain, skinTones: nil) - } else if rawValue == "🌤️" { - self.init(baseEmoji: .mostlySunny, skinTones: nil) - } else if rawValue == "🌥️" { - self.init(baseEmoji: .barelySunny, skinTones: nil) - } else if rawValue == "🌦️" { - self.init(baseEmoji: .partlySunnyRain, skinTones: nil) - } else if rawValue == "🌧️" { - self.init(baseEmoji: .rainCloud, skinTones: nil) - } else if rawValue == "🌨️" { - self.init(baseEmoji: .snowCloud, skinTones: nil) - } else if rawValue == "🌩️" { - self.init(baseEmoji: .lightning, skinTones: nil) - } else if rawValue == "🌪️" { - self.init(baseEmoji: .tornado, skinTones: nil) - } else if rawValue == "🌫️" { - self.init(baseEmoji: .fog, skinTones: nil) - } else if rawValue == "🌬️" { - self.init(baseEmoji: .windBlowingFace, skinTones: nil) - } else if rawValue == "🌀" { - self.init(baseEmoji: .cyclone, skinTones: nil) - } else if rawValue == "🌈" { - self.init(baseEmoji: .rainbow, skinTones: nil) - } else if rawValue == "🌂" { - self.init(baseEmoji: .closedUmbrella, skinTones: nil) - } else if rawValue == "☂️" { - self.init(baseEmoji: .umbrella, skinTones: nil) - } else if rawValue == "☔" { - self.init(baseEmoji: .umbrellaWithRainDrops, skinTones: nil) - } else if rawValue == "⛱️" { - self.init(baseEmoji: .umbrellaOnGround, skinTones: nil) - } else if rawValue == "⚡" { - self.init(baseEmoji: .zap, skinTones: nil) - } else if rawValue == "❄️" { - self.init(baseEmoji: .snowflake, skinTones: nil) - } else if rawValue == "☃️" { - self.init(baseEmoji: .snowman, skinTones: nil) - } else if rawValue == "⛄" { - self.init(baseEmoji: .snowmanWithoutSnow, skinTones: nil) - } else if rawValue == "☄️" { - self.init(baseEmoji: .comet, skinTones: nil) - } else if rawValue == "🔥" { - self.init(baseEmoji: .fire, skinTones: nil) - } else if rawValue == "💧" { - self.init(baseEmoji: .droplet, skinTones: nil) - } else if rawValue == "🌊" { - self.init(baseEmoji: .ocean, skinTones: nil) - } else if rawValue == "🎃" { - self.init(baseEmoji: .jackOLantern, skinTones: nil) - } else if rawValue == "🎄" { - self.init(baseEmoji: .christmasTree, skinTones: nil) - } else if rawValue == "🎆" { - self.init(baseEmoji: .fireworks, skinTones: nil) - } else if rawValue == "🎇" { - self.init(baseEmoji: .sparkler, skinTones: nil) - } else if rawValue == "🧨" { - self.init(baseEmoji: .firecracker, skinTones: nil) - } else if rawValue == "✨" { - self.init(baseEmoji: .sparkles, skinTones: nil) - } else if rawValue == "🎈" { - self.init(baseEmoji: .balloon, skinTones: nil) - } else if rawValue == "🎉" { - self.init(baseEmoji: .tada, skinTones: nil) - } else if rawValue == "🎊" { - self.init(baseEmoji: .confettiBall, skinTones: nil) - } else if rawValue == "🎋" { - self.init(baseEmoji: .tanabataTree, skinTones: nil) - } else if rawValue == "🎍" { - self.init(baseEmoji: .bamboo, skinTones: nil) - } else if rawValue == "🎎" { - self.init(baseEmoji: .dolls, skinTones: nil) - } else if rawValue == "🎏" { - self.init(baseEmoji: .flags, skinTones: nil) - } else if rawValue == "🎐" { - self.init(baseEmoji: .windChime, skinTones: nil) - } else if rawValue == "🎑" { - self.init(baseEmoji: .riceScene, skinTones: nil) - } else if rawValue == "🧧" { - self.init(baseEmoji: .redEnvelope, skinTones: nil) - } else if rawValue == "🎀" { - self.init(baseEmoji: .ribbon, skinTones: nil) - } else if rawValue == "🎁" { - self.init(baseEmoji: .gift, skinTones: nil) - } else if rawValue == "🎗️" { - self.init(baseEmoji: .reminderRibbon, skinTones: nil) - } else if rawValue == "🎟️" { - self.init(baseEmoji: .admissionTickets, skinTones: nil) - } else if rawValue == "🎫" { - self.init(baseEmoji: .ticket, skinTones: nil) - } else if rawValue == "🎖️" { - self.init(baseEmoji: .medal, skinTones: nil) - } else if rawValue == "🏆" { - self.init(baseEmoji: .trophy, skinTones: nil) - } else if rawValue == "🏅" { - self.init(baseEmoji: .sportsMedal, skinTones: nil) - } else if rawValue == "🥇" { - self.init(baseEmoji: .firstPlaceMedal, skinTones: nil) - } else if rawValue == "🥈" { - self.init(baseEmoji: .secondPlaceMedal, skinTones: nil) - } else if rawValue == "🥉" { - self.init(baseEmoji: .thirdPlaceMedal, skinTones: nil) - } else if rawValue == "⚽" { - self.init(baseEmoji: .soccer, skinTones: nil) - } else if rawValue == "⚾" { - self.init(baseEmoji: .baseball, skinTones: nil) - } else if rawValue == "🥎" { - self.init(baseEmoji: .softball, skinTones: nil) - } else if rawValue == "🏀" { - self.init(baseEmoji: .basketball, skinTones: nil) - } else if rawValue == "🏐" { - self.init(baseEmoji: .volleyball, skinTones: nil) - } else if rawValue == "🏈" { - self.init(baseEmoji: .football, skinTones: nil) - } else if rawValue == "🏉" { - self.init(baseEmoji: .rugbyFootball, skinTones: nil) - } else if rawValue == "🎾" { - self.init(baseEmoji: .tennis, skinTones: nil) - } else if rawValue == "🥏" { - self.init(baseEmoji: .flyingDisc, skinTones: nil) - } else if rawValue == "🎳" { - self.init(baseEmoji: .bowling, skinTones: nil) - } else if rawValue == "🏏" { - self.init(baseEmoji: .cricketBatAndBall, skinTones: nil) - } else if rawValue == "🏑" { - self.init(baseEmoji: .fieldHockeyStickAndBall, skinTones: nil) - } else if rawValue == "🏒" { - self.init(baseEmoji: .iceHockeyStickAndPuck, skinTones: nil) - } else if rawValue == "🥍" { - self.init(baseEmoji: .lacrosse, skinTones: nil) - } else if rawValue == "🏓" { - self.init(baseEmoji: .tableTennisPaddleAndBall, skinTones: nil) - } else if rawValue == "🏸" { - self.init(baseEmoji: .badmintonRacquetAndShuttlecock, skinTones: nil) - } else if rawValue == "🥊" { - self.init(baseEmoji: .boxingGlove, skinTones: nil) - } else if rawValue == "🥋" { - self.init(baseEmoji: .martialArtsUniform, skinTones: nil) - } else if rawValue == "🥅" { - self.init(baseEmoji: .goalNet, skinTones: nil) - } else if rawValue == "⛳" { - self.init(baseEmoji: .golf, skinTones: nil) - } else if rawValue == "⛸️" { - self.init(baseEmoji: .iceSkate, skinTones: nil) - } else if rawValue == "🎣" { - self.init(baseEmoji: .fishingPoleAndFish, skinTones: nil) - } else if rawValue == "🤿" { - self.init(baseEmoji: .divingMask, skinTones: nil) - } else if rawValue == "🎽" { - self.init(baseEmoji: .runningShirtWithSash, skinTones: nil) - } else if rawValue == "🎿" { - self.init(baseEmoji: .ski, skinTones: nil) - } else if rawValue == "🛷" { - self.init(baseEmoji: .sled, skinTones: nil) - } else if rawValue == "🥌" { - self.init(baseEmoji: .curlingStone, skinTones: nil) - } else if rawValue == "🎯" { - self.init(baseEmoji: .dart, skinTones: nil) - } else if rawValue == "🪀" { - self.init(baseEmoji: .yoYo, skinTones: nil) - } else if rawValue == "🪁" { - self.init(baseEmoji: .kite, skinTones: nil) - } else if rawValue == "🎱" { - self.init(baseEmoji: .eightBall, skinTones: nil) - } else if rawValue == "🔮" { - self.init(baseEmoji: .crystalBall, skinTones: nil) - } else if rawValue == "🪄" { - self.init(baseEmoji: .magicWand, skinTones: nil) - } else if rawValue == "🧿" { - self.init(baseEmoji: .nazarAmulet, skinTones: nil) - } else if rawValue == "🪬" { - self.init(baseEmoji: .hamsa, skinTones: nil) - } else if rawValue == "🎮" { - self.init(baseEmoji: .videoGame, skinTones: nil) - } else if rawValue == "🕹️" { - self.init(baseEmoji: .joystick, skinTones: nil) - } else if rawValue == "🎰" { - self.init(baseEmoji: .slotMachine, skinTones: nil) - } else if rawValue == "🎲" { - self.init(baseEmoji: .gameDie, skinTones: nil) - } else if rawValue == "🧩" { - self.init(baseEmoji: .jigsaw, skinTones: nil) - } else if rawValue == "🧸" { - self.init(baseEmoji: .teddyBear, skinTones: nil) - } else if rawValue == "🪅" { - self.init(baseEmoji: .pinata, skinTones: nil) - } else if rawValue == "🪩" { - self.init(baseEmoji: .mirrorBall, skinTones: nil) - } else if rawValue == "🪆" { - self.init(baseEmoji: .nestingDolls, skinTones: nil) - } else if rawValue == "♠️" { - self.init(baseEmoji: .spades, skinTones: nil) - } else if rawValue == "♥️" { - self.init(baseEmoji: .hearts, skinTones: nil) - } else if rawValue == "♦️" { - self.init(baseEmoji: .diamonds, skinTones: nil) - } else if rawValue == "♣️" { - self.init(baseEmoji: .clubs, skinTones: nil) - } else if rawValue == "♟️" { - self.init(baseEmoji: .chessPawn, skinTones: nil) - } else if rawValue == "🃏" { - self.init(baseEmoji: .blackJoker, skinTones: nil) - } else if rawValue == "🀄" { - self.init(baseEmoji: .mahjong, skinTones: nil) - } else if rawValue == "🎴" { - self.init(baseEmoji: .flowerPlayingCards, skinTones: nil) - } else if rawValue == "🎭" { - self.init(baseEmoji: .performingArts, skinTones: nil) - } else if rawValue == "🖼️" { - self.init(baseEmoji: .frameWithPicture, skinTones: nil) - } else if rawValue == "🎨" { - self.init(baseEmoji: .art, skinTones: nil) - } else if rawValue == "🧵" { - self.init(baseEmoji: .thread, skinTones: nil) - } else if rawValue == "🪡" { - self.init(baseEmoji: .sewingNeedle, skinTones: nil) - } else if rawValue == "🧶" { - self.init(baseEmoji: .yarn, skinTones: nil) - } else if rawValue == "🪢" { - self.init(baseEmoji: .knot, skinTones: nil) - } else if rawValue == "👓" { - self.init(baseEmoji: .eyeglasses, skinTones: nil) - } else if rawValue == "🕶️" { - self.init(baseEmoji: .darkSunglasses, skinTones: nil) - } else if rawValue == "🥽" { - self.init(baseEmoji: .goggles, skinTones: nil) - } else if rawValue == "🥼" { - self.init(baseEmoji: .labCoat, skinTones: nil) - } else if rawValue == "🦺" { - self.init(baseEmoji: .safetyVest, skinTones: nil) - } else if rawValue == "👔" { - self.init(baseEmoji: .necktie, skinTones: nil) - } else if rawValue == "👕" { - self.init(baseEmoji: .shirt, skinTones: nil) - } else if rawValue == "👖" { - self.init(baseEmoji: .jeans, skinTones: nil) - } else if rawValue == "🧣" { - self.init(baseEmoji: .scarf, skinTones: nil) - } else if rawValue == "🧤" { - self.init(baseEmoji: .gloves, skinTones: nil) - } else if rawValue == "🧥" { - self.init(baseEmoji: .coat, skinTones: nil) - } else if rawValue == "🧦" { - self.init(baseEmoji: .socks, skinTones: nil) - } else if rawValue == "👗" { - self.init(baseEmoji: .dress, skinTones: nil) - } else if rawValue == "👘" { - self.init(baseEmoji: .kimono, skinTones: nil) - } else if rawValue == "🥻" { - self.init(baseEmoji: .sari, skinTones: nil) - } else if rawValue == "🩱" { - self.init(baseEmoji: .onePieceSwimsuit, skinTones: nil) - } else if rawValue == "🩲" { - self.init(baseEmoji: .briefs, skinTones: nil) - } else if rawValue == "🩳" { - self.init(baseEmoji: .shorts, skinTones: nil) - } else if rawValue == "👙" { - self.init(baseEmoji: .bikini, skinTones: nil) - } else if rawValue == "👚" { - self.init(baseEmoji: .womansClothes, skinTones: nil) - } else if rawValue == "👛" { - self.init(baseEmoji: .purse, skinTones: nil) - } else if rawValue == "👜" { - self.init(baseEmoji: .handbag, skinTones: nil) - } else if rawValue == "👝" { - self.init(baseEmoji: .pouch, skinTones: nil) - } else if rawValue == "🛍️" { - self.init(baseEmoji: .shoppingBags, skinTones: nil) - } else if rawValue == "🎒" { - self.init(baseEmoji: .schoolSatchel, skinTones: nil) - } else if rawValue == "🩴" { - self.init(baseEmoji: .thongSandal, skinTones: nil) - } else if rawValue == "👞" { - self.init(baseEmoji: .mansShoe, skinTones: nil) - } else if rawValue == "👟" { - self.init(baseEmoji: .athleticShoe, skinTones: nil) - } else if rawValue == "🥾" { - self.init(baseEmoji: .hikingBoot, skinTones: nil) - } else if rawValue == "🥿" { - self.init(baseEmoji: .womansFlatShoe, skinTones: nil) - } else if rawValue == "👠" { - self.init(baseEmoji: .highHeel, skinTones: nil) - } else if rawValue == "👡" { - self.init(baseEmoji: .sandal, skinTones: nil) - } else if rawValue == "🩰" { - self.init(baseEmoji: .balletShoes, skinTones: nil) - } else if rawValue == "👢" { - self.init(baseEmoji: .boot, skinTones: nil) - } else if rawValue == "👑" { - self.init(baseEmoji: .crown, skinTones: nil) - } else if rawValue == "👒" { - self.init(baseEmoji: .womansHat, skinTones: nil) - } else if rawValue == "🎩" { - self.init(baseEmoji: .tophat, skinTones: nil) - } else if rawValue == "🎓" { - self.init(baseEmoji: .mortarBoard, skinTones: nil) - } else if rawValue == "🧢" { - self.init(baseEmoji: .billedCap, skinTones: nil) - } else if rawValue == "🪖" { - self.init(baseEmoji: .militaryHelmet, skinTones: nil) - } else if rawValue == "⛑️" { - self.init(baseEmoji: .helmetWithWhiteCross, skinTones: nil) - } else if rawValue == "📿" { - self.init(baseEmoji: .prayerBeads, skinTones: nil) - } else if rawValue == "💄" { - self.init(baseEmoji: .lipstick, skinTones: nil) - } else if rawValue == "💍" { - self.init(baseEmoji: .ring, skinTones: nil) - } else if rawValue == "💎" { - self.init(baseEmoji: .gem, skinTones: nil) - } else if rawValue == "🔇" { - self.init(baseEmoji: .mute, skinTones: nil) - } else if rawValue == "🔈" { - self.init(baseEmoji: .speaker, skinTones: nil) - } else if rawValue == "🔉" { - self.init(baseEmoji: .sound, skinTones: nil) - } else if rawValue == "🔊" { - self.init(baseEmoji: .loudSound, skinTones: nil) - } else if rawValue == "📢" { - self.init(baseEmoji: .loudspeaker, skinTones: nil) - } else if rawValue == "📣" { - self.init(baseEmoji: .mega, skinTones: nil) - } else if rawValue == "📯" { - self.init(baseEmoji: .postalHorn, skinTones: nil) - } else if rawValue == "🔔" { - self.init(baseEmoji: .bell, skinTones: nil) - } else if rawValue == "🔕" { - self.init(baseEmoji: .noBell, skinTones: nil) - } else if rawValue == "🎼" { - self.init(baseEmoji: .musicalScore, skinTones: nil) - } else if rawValue == "🎵" { - self.init(baseEmoji: .musicalNote, skinTones: nil) - } else if rawValue == "🎶" { - self.init(baseEmoji: .notes, skinTones: nil) - } else if rawValue == "🎙️" { - self.init(baseEmoji: .studioMicrophone, skinTones: nil) - } else if rawValue == "🎚️" { - self.init(baseEmoji: .levelSlider, skinTones: nil) - } else if rawValue == "🎛️" { - self.init(baseEmoji: .controlKnobs, skinTones: nil) - } else if rawValue == "🎤" { - self.init(baseEmoji: .microphone, skinTones: nil) - } else if rawValue == "🎧" { - self.init(baseEmoji: .headphones, skinTones: nil) - } else if rawValue == "📻" { - self.init(baseEmoji: .radio, skinTones: nil) - } else if rawValue == "🎷" { - self.init(baseEmoji: .saxophone, skinTones: nil) - } else if rawValue == "🪗" { - self.init(baseEmoji: .accordion, skinTones: nil) - } else if rawValue == "🎸" { - self.init(baseEmoji: .guitar, skinTones: nil) - } else if rawValue == "🎹" { - self.init(baseEmoji: .musicalKeyboard, skinTones: nil) - } else if rawValue == "🎺" { - self.init(baseEmoji: .trumpet, skinTones: nil) - } else if rawValue == "🎻" { - self.init(baseEmoji: .violin, skinTones: nil) - } else if rawValue == "🪕" { - self.init(baseEmoji: .banjo, skinTones: nil) - } else if rawValue == "🥁" { - self.init(baseEmoji: .drumWithDrumsticks, skinTones: nil) - } else if rawValue == "🪘" { - self.init(baseEmoji: .longDrum, skinTones: nil) - } else if rawValue == "📱" { - self.init(baseEmoji: .iphone, skinTones: nil) - } else if rawValue == "📲" { - self.init(baseEmoji: .calling, skinTones: nil) - } else if rawValue == "☎️" { - self.init(baseEmoji: .phone, skinTones: nil) - } else if rawValue == "📞" { - self.init(baseEmoji: .telephoneReceiver, skinTones: nil) - } else if rawValue == "📟" { - self.init(baseEmoji: .pager, skinTones: nil) - } else if rawValue == "📠" { - self.init(baseEmoji: .fax, skinTones: nil) - } else if rawValue == "🔋" { - self.init(baseEmoji: .battery, skinTones: nil) - } else if rawValue == "🪫" { - self.init(baseEmoji: .lowBattery, skinTones: nil) - } else if rawValue == "🔌" { - self.init(baseEmoji: .electricPlug, skinTones: nil) - } else if rawValue == "💻" { - self.init(baseEmoji: .computer, skinTones: nil) - } else if rawValue == "🖥️" { - self.init(baseEmoji: .desktopComputer, skinTones: nil) - } else if rawValue == "🖨️" { - self.init(baseEmoji: .printer, skinTones: nil) - } else if rawValue == "⌨️" { - self.init(baseEmoji: .keyboard, skinTones: nil) - } else if rawValue == "🖱️" { - self.init(baseEmoji: .threeButtonMouse, skinTones: nil) - } else if rawValue == "🖲️" { - self.init(baseEmoji: .trackball, skinTones: nil) - } else if rawValue == "💽" { - self.init(baseEmoji: .minidisc, skinTones: nil) - } else if rawValue == "💾" { - self.init(baseEmoji: .floppyDisk, skinTones: nil) - } else if rawValue == "💿" { - self.init(baseEmoji: .cd, skinTones: nil) - } else if rawValue == "📀" { - self.init(baseEmoji: .dvd, skinTones: nil) - } else if rawValue == "🧮" { - self.init(baseEmoji: .abacus, skinTones: nil) - } else if rawValue == "🎥" { - self.init(baseEmoji: .movieCamera, skinTones: nil) - } else if rawValue == "🎞️" { - self.init(baseEmoji: .filmFrames, skinTones: nil) - } else if rawValue == "📽️" { - self.init(baseEmoji: .filmProjector, skinTones: nil) - } else if rawValue == "🎬" { - self.init(baseEmoji: .clapper, skinTones: nil) - } else if rawValue == "📺" { - self.init(baseEmoji: .tv, skinTones: nil) - } else if rawValue == "📷" { - self.init(baseEmoji: .camera, skinTones: nil) - } else if rawValue == "📸" { - self.init(baseEmoji: .cameraWithFlash, skinTones: nil) - } else if rawValue == "📹" { - self.init(baseEmoji: .videoCamera, skinTones: nil) - } else if rawValue == "📼" { - self.init(baseEmoji: .vhs, skinTones: nil) - } else if rawValue == "🔍" { - self.init(baseEmoji: .mag, skinTones: nil) - } else if rawValue == "🔎" { - self.init(baseEmoji: .magRight, skinTones: nil) - } else if rawValue == "🕯️" { - self.init(baseEmoji: .candle, skinTones: nil) - } else if rawValue == "💡" { - self.init(baseEmoji: .bulb, skinTones: nil) - } else if rawValue == "🔦" { - self.init(baseEmoji: .flashlight, skinTones: nil) - } else if rawValue == "🏮" { - self.init(baseEmoji: .izakayaLantern, skinTones: nil) - } else if rawValue == "🪔" { - self.init(baseEmoji: .diyaLamp, skinTones: nil) - } else if rawValue == "📔" { - self.init(baseEmoji: .notebookWithDecorativeCover, skinTones: nil) - } else if rawValue == "📕" { - self.init(baseEmoji: .closedBook, skinTones: nil) - } else if rawValue == "📖" { - self.init(baseEmoji: .book, skinTones: nil) - } else if rawValue == "📗" { - self.init(baseEmoji: .greenBook, skinTones: nil) - } else if rawValue == "📘" { - self.init(baseEmoji: .blueBook, skinTones: nil) - } else if rawValue == "📙" { - self.init(baseEmoji: .orangeBook, skinTones: nil) - } else if rawValue == "📚" { - self.init(baseEmoji: .books, skinTones: nil) - } else if rawValue == "📓" { - self.init(baseEmoji: .notebook, skinTones: nil) - } else if rawValue == "📒" { - self.init(baseEmoji: .ledger, skinTones: nil) - } else if rawValue == "📃" { - self.init(baseEmoji: .pageWithCurl, skinTones: nil) - } else if rawValue == "📜" { - self.init(baseEmoji: .scroll, skinTones: nil) - } else if rawValue == "📄" { - self.init(baseEmoji: .pageFacingUp, skinTones: nil) - } else if rawValue == "📰" { - self.init(baseEmoji: .newspaper, skinTones: nil) - } else if rawValue == "🗞️" { - self.init(baseEmoji: .rolledUpNewspaper, skinTones: nil) - } else if rawValue == "📑" { - self.init(baseEmoji: .bookmarkTabs, skinTones: nil) - } else if rawValue == "🔖" { - self.init(baseEmoji: .bookmark, skinTones: nil) - } else if rawValue == "🏷️" { - self.init(baseEmoji: .label, skinTones: nil) - } else if rawValue == "💰" { - self.init(baseEmoji: .moneybag, skinTones: nil) - } else if rawValue == "🪙" { - self.init(baseEmoji: .coin, skinTones: nil) - } else if rawValue == "💴" { - self.init(baseEmoji: .yen, skinTones: nil) - } else if rawValue == "💵" { - self.init(baseEmoji: .dollar, skinTones: nil) - } else if rawValue == "💶" { - self.init(baseEmoji: .euro, skinTones: nil) - } else if rawValue == "💷" { - self.init(baseEmoji: .pound, skinTones: nil) - } else if rawValue == "💸" { - self.init(baseEmoji: .moneyWithWings, skinTones: nil) - } else if rawValue == "💳" { - self.init(baseEmoji: .creditCard, skinTones: nil) - } else if rawValue == "🧾" { - self.init(baseEmoji: .receipt, skinTones: nil) - } else if rawValue == "💹" { - self.init(baseEmoji: .chart, skinTones: nil) - } else if rawValue == "✉️" { - self.init(baseEmoji: .email, skinTones: nil) - } else if rawValue == "📧" { - self.init(baseEmoji: .eMail, skinTones: nil) - } else if rawValue == "📨" { - self.init(baseEmoji: .incomingEnvelope, skinTones: nil) - } else if rawValue == "📩" { - self.init(baseEmoji: .envelopeWithArrow, skinTones: nil) - } else if rawValue == "📤" { - self.init(baseEmoji: .outboxTray, skinTones: nil) - } else if rawValue == "📥" { - self.init(baseEmoji: .inboxTray, skinTones: nil) - } else if rawValue == "📦" { - self.init(baseEmoji: .package, skinTones: nil) - } else if rawValue == "📫" { - self.init(baseEmoji: .mailbox, skinTones: nil) - } else if rawValue == "📪" { - self.init(baseEmoji: .mailboxClosed, skinTones: nil) - } else if rawValue == "📬" { - self.init(baseEmoji: .mailboxWithMail, skinTones: nil) - } else if rawValue == "📭" { - self.init(baseEmoji: .mailboxWithNoMail, skinTones: nil) - } else if rawValue == "📮" { - self.init(baseEmoji: .postbox, skinTones: nil) - } else if rawValue == "🗳️" { - self.init(baseEmoji: .ballotBoxWithBallot, skinTones: nil) - } else if rawValue == "✏️" { - self.init(baseEmoji: .pencil2, skinTones: nil) - } else if rawValue == "✒️" { - self.init(baseEmoji: .blackNib, skinTones: nil) - } else if rawValue == "🖋️" { - self.init(baseEmoji: .lowerLeftFountainPen, skinTones: nil) - } else if rawValue == "🖊️" { - self.init(baseEmoji: .lowerLeftBallpointPen, skinTones: nil) - } else if rawValue == "🖌️" { - self.init(baseEmoji: .lowerLeftPaintbrush, skinTones: nil) - } else if rawValue == "🖍️" { - self.init(baseEmoji: .lowerLeftCrayon, skinTones: nil) - } else if rawValue == "📝" { - self.init(baseEmoji: .memo, skinTones: nil) - } else if rawValue == "💼" { - self.init(baseEmoji: .briefcase, skinTones: nil) - } else if rawValue == "📁" { - self.init(baseEmoji: .fileFolder, skinTones: nil) - } else if rawValue == "📂" { - self.init(baseEmoji: .openFileFolder, skinTones: nil) - } else if rawValue == "🗂️" { - self.init(baseEmoji: .cardIndexDividers, skinTones: nil) - } else if rawValue == "📅" { - self.init(baseEmoji: .date, skinTones: nil) - } else if rawValue == "📆" { - self.init(baseEmoji: .calendar, skinTones: nil) - } else if rawValue == "🗒️" { - self.init(baseEmoji: .spiralNotePad, skinTones: nil) - } else if rawValue == "🗓️" { - self.init(baseEmoji: .spiralCalendarPad, skinTones: nil) - } else if rawValue == "📇" { - self.init(baseEmoji: .cardIndex, skinTones: nil) - } else if rawValue == "📈" { - self.init(baseEmoji: .chartWithUpwardsTrend, skinTones: nil) - } else if rawValue == "📉" { - self.init(baseEmoji: .chartWithDownwardsTrend, skinTones: nil) - } else if rawValue == "📊" { - self.init(baseEmoji: .barChart, skinTones: nil) - } else if rawValue == "📋" { - self.init(baseEmoji: .clipboard, skinTones: nil) - } else if rawValue == "📌" { - self.init(baseEmoji: .pushpin, skinTones: nil) - } else if rawValue == "📍" { - self.init(baseEmoji: .roundPushpin, skinTones: nil) - } else if rawValue == "📎" { - self.init(baseEmoji: .paperclip, skinTones: nil) - } else if rawValue == "🖇️" { - self.init(baseEmoji: .linkedPaperclips, skinTones: nil) - } else if rawValue == "📏" { - self.init(baseEmoji: .straightRuler, skinTones: nil) - } else if rawValue == "📐" { - self.init(baseEmoji: .triangularRuler, skinTones: nil) - } else if rawValue == "✂️" { - self.init(baseEmoji: .scissors, skinTones: nil) - } else if rawValue == "🗃️" { - self.init(baseEmoji: .cardFileBox, skinTones: nil) - } else if rawValue == "🗄️" { - self.init(baseEmoji: .fileCabinet, skinTones: nil) - } else if rawValue == "🗑️" { - self.init(baseEmoji: .wastebasket, skinTones: nil) - } else if rawValue == "🔒" { - self.init(baseEmoji: .lock, skinTones: nil) - } else if rawValue == "🔓" { - self.init(baseEmoji: .unlock, skinTones: nil) - } else if rawValue == "🔏" { - self.init(baseEmoji: .lockWithInkPen, skinTones: nil) - } else if rawValue == "🔐" { - self.init(baseEmoji: .closedLockWithKey, skinTones: nil) - } else if rawValue == "🔑" { - self.init(baseEmoji: .key, skinTones: nil) - } else if rawValue == "🗝️" { - self.init(baseEmoji: .oldKey, skinTones: nil) - } else if rawValue == "🔨" { - self.init(baseEmoji: .hammer, skinTones: nil) - } else if rawValue == "🪓" { - self.init(baseEmoji: .axe, skinTones: nil) - } else if rawValue == "⛏️" { - self.init(baseEmoji: .pick, skinTones: nil) - } else if rawValue == "⚒️" { - self.init(baseEmoji: .hammerAndPick, skinTones: nil) - } else if rawValue == "🛠️" { - self.init(baseEmoji: .hammerAndWrench, skinTones: nil) - } else if rawValue == "🗡️" { - self.init(baseEmoji: .daggerKnife, skinTones: nil) - } else if rawValue == "⚔️" { - self.init(baseEmoji: .crossedSwords, skinTones: nil) - } else if rawValue == "🔫" { - self.init(baseEmoji: .gun, skinTones: nil) - } else if rawValue == "🪃" { - self.init(baseEmoji: .boomerang, skinTones: nil) - } else if rawValue == "🏹" { - self.init(baseEmoji: .bowAndArrow, skinTones: nil) - } else if rawValue == "🛡️" { - self.init(baseEmoji: .shield, skinTones: nil) - } else if rawValue == "🪚" { - self.init(baseEmoji: .carpentrySaw, skinTones: nil) - } else if rawValue == "🔧" { - self.init(baseEmoji: .wrench, skinTones: nil) - } else if rawValue == "🪛" { - self.init(baseEmoji: .screwdriver, skinTones: nil) - } else if rawValue == "🔩" { - self.init(baseEmoji: .nutAndBolt, skinTones: nil) - } else if rawValue == "⚙️" { - self.init(baseEmoji: .gear, skinTones: nil) - } else if rawValue == "🗜️" { - self.init(baseEmoji: .compression, skinTones: nil) - } else if rawValue == "⚖️" { - self.init(baseEmoji: .scales, skinTones: nil) - } else if rawValue == "🦯" { - self.init(baseEmoji: .probingCane, skinTones: nil) - } else if rawValue == "🔗" { - self.init(baseEmoji: .link, skinTones: nil) - } else if rawValue == "⛓️" { - self.init(baseEmoji: .chains, skinTones: nil) - } else if rawValue == "🪝" { - self.init(baseEmoji: .hook, skinTones: nil) - } else if rawValue == "🧰" { - self.init(baseEmoji: .toolbox, skinTones: nil) - } else if rawValue == "🧲" { - self.init(baseEmoji: .magnet, skinTones: nil) - } else if rawValue == "🪜" { - self.init(baseEmoji: .ladder, skinTones: nil) - } else if rawValue == "⚗️" { - self.init(baseEmoji: .alembic, skinTones: nil) - } else if rawValue == "🧪" { - self.init(baseEmoji: .testTube, skinTones: nil) - } else if rawValue == "🧫" { - self.init(baseEmoji: .petriDish, skinTones: nil) - } else if rawValue == "🧬" { - self.init(baseEmoji: .dna, skinTones: nil) - } else if rawValue == "🔬" { - self.init(baseEmoji: .microscope, skinTones: nil) - } else if rawValue == "🔭" { - self.init(baseEmoji: .telescope, skinTones: nil) - } else if rawValue == "📡" { - self.init(baseEmoji: .satelliteAntenna, skinTones: nil) - } else if rawValue == "💉" { - self.init(baseEmoji: .syringe, skinTones: nil) - } else if rawValue == "🩸" { - self.init(baseEmoji: .dropOfBlood, skinTones: nil) - } else if rawValue == "💊" { - self.init(baseEmoji: .pill, skinTones: nil) - } else if rawValue == "🩹" { - self.init(baseEmoji: .adhesiveBandage, skinTones: nil) - } else if rawValue == "🩼" { - self.init(baseEmoji: .crutch, skinTones: nil) - } else if rawValue == "🩺" { - self.init(baseEmoji: .stethoscope, skinTones: nil) - } else if rawValue == "🩻" { - self.init(baseEmoji: .xRay, skinTones: nil) - } else if rawValue == "🚪" { - self.init(baseEmoji: .door, skinTones: nil) - } else if rawValue == "🛗" { - self.init(baseEmoji: .elevator, skinTones: nil) - } else if rawValue == "🪞" { - self.init(baseEmoji: .mirror, skinTones: nil) - } else if rawValue == "🪟" { - self.init(baseEmoji: .window, skinTones: nil) - } else if rawValue == "🛏️" { - self.init(baseEmoji: .bed, skinTones: nil) - } else if rawValue == "🛋️" { - self.init(baseEmoji: .couchAndLamp, skinTones: nil) - } else if rawValue == "🪑" { - self.init(baseEmoji: .chair, skinTones: nil) - } else if rawValue == "🚽" { - self.init(baseEmoji: .toilet, skinTones: nil) - } else if rawValue == "🪠" { - self.init(baseEmoji: .plunger, skinTones: nil) - } else if rawValue == "🚿" { - self.init(baseEmoji: .shower, skinTones: nil) - } else if rawValue == "🛁" { - self.init(baseEmoji: .bathtub, skinTones: nil) - } else if rawValue == "🪤" { - self.init(baseEmoji: .mouseTrap, skinTones: nil) - } else if rawValue == "🪒" { - self.init(baseEmoji: .razor, skinTones: nil) - } else if rawValue == "🧴" { - self.init(baseEmoji: .lotionBottle, skinTones: nil) - } else if rawValue == "🧷" { - self.init(baseEmoji: .safetyPin, skinTones: nil) - } else if rawValue == "🧹" { - self.init(baseEmoji: .broom, skinTones: nil) - } else if rawValue == "🧺" { - self.init(baseEmoji: .basket, skinTones: nil) - } else if rawValue == "🧻" { - self.init(baseEmoji: .rollOfPaper, skinTones: nil) - } else if rawValue == "🪣" { - self.init(baseEmoji: .bucket, skinTones: nil) - } else if rawValue == "🧼" { - self.init(baseEmoji: .soap, skinTones: nil) - } else if rawValue == "🫧" { - self.init(baseEmoji: .bubbles, skinTones: nil) - } else if rawValue == "🪥" { - self.init(baseEmoji: .toothbrush, skinTones: nil) - } else if rawValue == "🧽" { - self.init(baseEmoji: .sponge, skinTones: nil) - } else if rawValue == "🧯" { - self.init(baseEmoji: .fireExtinguisher, skinTones: nil) - } else if rawValue == "🛒" { - self.init(baseEmoji: .shoppingTrolley, skinTones: nil) - } else if rawValue == "🚬" { - self.init(baseEmoji: .smoking, skinTones: nil) - } else if rawValue == "⚰️" { - self.init(baseEmoji: .coffin, skinTones: nil) - } else if rawValue == "🪦" { - self.init(baseEmoji: .headstone, skinTones: nil) - } else if rawValue == "⚱️" { - self.init(baseEmoji: .funeralUrn, skinTones: nil) - } else if rawValue == "🗿" { - self.init(baseEmoji: .moyai, skinTones: nil) - } else if rawValue == "🪧" { - self.init(baseEmoji: .placard, skinTones: nil) - } else if rawValue == "🪪" { - self.init(baseEmoji: .identificationCard, skinTones: nil) - } else if rawValue == "🏧" { - self.init(baseEmoji: .atm, skinTones: nil) - } else if rawValue == "🚮" { - self.init(baseEmoji: .putLitterInItsPlace, skinTones: nil) - } else if rawValue == "🚰" { - self.init(baseEmoji: .potableWater, skinTones: nil) - } else if rawValue == "♿" { - self.init(baseEmoji: .wheelchair, skinTones: nil) - } else if rawValue == "🚹" { - self.init(baseEmoji: .mens, skinTones: nil) - } else if rawValue == "🚺" { - self.init(baseEmoji: .womens, skinTones: nil) - } else if rawValue == "🚻" { - self.init(baseEmoji: .restroom, skinTones: nil) - } else if rawValue == "🚼" { - self.init(baseEmoji: .babySymbol, skinTones: nil) - } else if rawValue == "🚾" { - self.init(baseEmoji: .wc, skinTones: nil) - } else if rawValue == "🛂" { - self.init(baseEmoji: .passportControl, skinTones: nil) - } else if rawValue == "🛃" { - self.init(baseEmoji: .customs, skinTones: nil) - } else if rawValue == "🛄" { - self.init(baseEmoji: .baggageClaim, skinTones: nil) - } else if rawValue == "🛅" { - self.init(baseEmoji: .leftLuggage, skinTones: nil) - } else if rawValue == "⚠️" { - self.init(baseEmoji: .warning, skinTones: nil) - } else if rawValue == "🚸" { - self.init(baseEmoji: .childrenCrossing, skinTones: nil) - } else if rawValue == "⛔" { - self.init(baseEmoji: .noEntry, skinTones: nil) - } else if rawValue == "🚫" { - self.init(baseEmoji: .noEntrySign, skinTones: nil) - } else if rawValue == "🚳" { - self.init(baseEmoji: .noBicycles, skinTones: nil) - } else if rawValue == "🚭" { - self.init(baseEmoji: .noSmoking, skinTones: nil) - } else if rawValue == "🚯" { - self.init(baseEmoji: .doNotLitter, skinTones: nil) - } else if rawValue == "🚱" { - self.init(baseEmoji: .nonPotableWater, skinTones: nil) - } else if rawValue == "🚷" { - self.init(baseEmoji: .noPedestrians, skinTones: nil) - } else if rawValue == "📵" { - self.init(baseEmoji: .noMobilePhones, skinTones: nil) - } else if rawValue == "🔞" { - self.init(baseEmoji: .underage, skinTones: nil) - } else if rawValue == "☢️" { - self.init(baseEmoji: .radioactiveSign, skinTones: nil) - } else if rawValue == "☣️" { - self.init(baseEmoji: .biohazardSign, skinTones: nil) - } else if rawValue == "⬆️" { - self.init(baseEmoji: .arrowUp, skinTones: nil) - } else if rawValue == "↗️" { - self.init(baseEmoji: .arrowUpperRight, skinTones: nil) - } else if rawValue == "➡️" { - self.init(baseEmoji: .arrowRight, skinTones: nil) - } else if rawValue == "↘️" { - self.init(baseEmoji: .arrowLowerRight, skinTones: nil) - } else if rawValue == "⬇️" { - self.init(baseEmoji: .arrowDown, skinTones: nil) - } else if rawValue == "↙️" { - self.init(baseEmoji: .arrowLowerLeft, skinTones: nil) - } else if rawValue == "⬅️" { - self.init(baseEmoji: .arrowLeft, skinTones: nil) - } else if rawValue == "↖️" { - self.init(baseEmoji: .arrowUpperLeft, skinTones: nil) - } else if rawValue == "↕️" { - self.init(baseEmoji: .arrowUpDown, skinTones: nil) - } else if rawValue == "↔️" { - self.init(baseEmoji: .leftRightArrow, skinTones: nil) - } else if rawValue == "↩️" { - self.init(baseEmoji: .leftwardsArrowWithHook, skinTones: nil) - } else if rawValue == "↪️" { - self.init(baseEmoji: .arrowRightHook, skinTones: nil) - } else if rawValue == "⤴️" { - self.init(baseEmoji: .arrowHeadingUp, skinTones: nil) - } else if rawValue == "⤵️" { - self.init(baseEmoji: .arrowHeadingDown, skinTones: nil) - } else if rawValue == "🔃" { - self.init(baseEmoji: .arrowsClockwise, skinTones: nil) - } else if rawValue == "🔄" { - self.init(baseEmoji: .arrowsCounterclockwise, skinTones: nil) - } else if rawValue == "🔙" { - self.init(baseEmoji: .back, skinTones: nil) - } else if rawValue == "🔚" { - self.init(baseEmoji: .end, skinTones: nil) - } else if rawValue == "🔛" { - self.init(baseEmoji: .on, skinTones: nil) - } else if rawValue == "🔜" { - self.init(baseEmoji: .soon, skinTones: nil) - } else if rawValue == "🔝" { - self.init(baseEmoji: .top, skinTones: nil) - } else if rawValue == "🛐" { - self.init(baseEmoji: .placeOfWorship, skinTones: nil) - } else if rawValue == "⚛️" { - self.init(baseEmoji: .atomSymbol, skinTones: nil) - } else if rawValue == "🕉️" { - self.init(baseEmoji: .omSymbol, skinTones: nil) - } else if rawValue == "✡️" { - self.init(baseEmoji: .starOfDavid, skinTones: nil) - } else if rawValue == "☸️" { - self.init(baseEmoji: .wheelOfDharma, skinTones: nil) - } else if rawValue == "☯️" { - self.init(baseEmoji: .yinYang, skinTones: nil) - } else if rawValue == "✝️" { - self.init(baseEmoji: .latinCross, skinTones: nil) - } else if rawValue == "☦️" { - self.init(baseEmoji: .orthodoxCross, skinTones: nil) - } else if rawValue == "☪️" { - self.init(baseEmoji: .starAndCrescent, skinTones: nil) - } else if rawValue == "☮️" { - self.init(baseEmoji: .peaceSymbol, skinTones: nil) - } else if rawValue == "🕎" { - self.init(baseEmoji: .menorahWithNineBranches, skinTones: nil) - } else if rawValue == "🔯" { - self.init(baseEmoji: .sixPointedStar, skinTones: nil) - } else if rawValue == "♈" { - self.init(baseEmoji: .aries, skinTones: nil) - } else if rawValue == "♉" { - self.init(baseEmoji: .taurus, skinTones: nil) - } else if rawValue == "♊" { - self.init(baseEmoji: .gemini, skinTones: nil) - } else if rawValue == "♋" { - self.init(baseEmoji: .cancer, skinTones: nil) - } else if rawValue == "♌" { - self.init(baseEmoji: .leo, skinTones: nil) - } else if rawValue == "♍" { - self.init(baseEmoji: .virgo, skinTones: nil) - } else if rawValue == "♎" { - self.init(baseEmoji: .libra, skinTones: nil) - } else if rawValue == "♏" { - self.init(baseEmoji: .scorpius, skinTones: nil) - } else if rawValue == "♐" { - self.init(baseEmoji: .sagittarius, skinTones: nil) - } else if rawValue == "♑" { - self.init(baseEmoji: .capricorn, skinTones: nil) - } else if rawValue == "♒" { - self.init(baseEmoji: .aquarius, skinTones: nil) - } else if rawValue == "♓" { - self.init(baseEmoji: .pisces, skinTones: nil) - } else if rawValue == "⛎" { - self.init(baseEmoji: .ophiuchus, skinTones: nil) - } else if rawValue == "🔀" { - self.init(baseEmoji: .twistedRightwardsArrows, skinTones: nil) - } else if rawValue == "🔁" { - self.init(baseEmoji: .`repeat`, skinTones: nil) - } else if rawValue == "🔂" { - self.init(baseEmoji: .repeatOne, skinTones: nil) - } else if rawValue == "▶️" { - self.init(baseEmoji: .arrowForward, skinTones: nil) - } else if rawValue == "⏩" { - self.init(baseEmoji: .fastForward, skinTones: nil) - } else if rawValue == "⏭️" { - self.init(baseEmoji: .blackRightPointingDoubleTriangleWithVerticalBar, skinTones: nil) - } else if rawValue == "⏯️" { - self.init(baseEmoji: .blackRightPointingTriangleWithDoubleVerticalBar, skinTones: nil) - } else if rawValue == "◀️" { - self.init(baseEmoji: .arrowBackward, skinTones: nil) - } else if rawValue == "⏪" { - self.init(baseEmoji: .rewind, skinTones: nil) - } else if rawValue == "⏮️" { - self.init(baseEmoji: .blackLeftPointingDoubleTriangleWithVerticalBar, skinTones: nil) - } else if rawValue == "🔼" { - self.init(baseEmoji: .arrowUpSmall, skinTones: nil) - } else if rawValue == "⏫" { - self.init(baseEmoji: .arrowDoubleUp, skinTones: nil) - } else if rawValue == "🔽" { - self.init(baseEmoji: .arrowDownSmall, skinTones: nil) - } else if rawValue == "⏬" { - self.init(baseEmoji: .arrowDoubleDown, skinTones: nil) - } else if rawValue == "⏸️" { - self.init(baseEmoji: .doubleVerticalBar, skinTones: nil) - } else if rawValue == "⏹️" { - self.init(baseEmoji: .blackSquareForStop, skinTones: nil) - } else if rawValue == "⏺️" { - self.init(baseEmoji: .blackCircleForRecord, skinTones: nil) - } else if rawValue == "⏏️" { - self.init(baseEmoji: .eject, skinTones: nil) - } else if rawValue == "🎦" { - self.init(baseEmoji: .cinema, skinTones: nil) - } else if rawValue == "🔅" { - self.init(baseEmoji: .lowBrightness, skinTones: nil) - } else if rawValue == "🔆" { - self.init(baseEmoji: .highBrightness, skinTones: nil) - } else if rawValue == "📶" { - self.init(baseEmoji: .signalStrength, skinTones: nil) - } else if rawValue == "📳" { - self.init(baseEmoji: .vibrationMode, skinTones: nil) - } else if rawValue == "📴" { - self.init(baseEmoji: .mobilePhoneOff, skinTones: nil) - } else if rawValue == "♀️" { - self.init(baseEmoji: .femaleSign, skinTones: nil) - } else if rawValue == "♂️" { - self.init(baseEmoji: .maleSign, skinTones: nil) - } else if rawValue == "⚧️" { - self.init(baseEmoji: .transgenderSymbol, skinTones: nil) - } else if rawValue == "✖️" { - self.init(baseEmoji: .heavyMultiplicationX, skinTones: nil) - } else if rawValue == "➕" { - self.init(baseEmoji: .heavyPlusSign, skinTones: nil) - } else if rawValue == "➖" { - self.init(baseEmoji: .heavyMinusSign, skinTones: nil) - } else if rawValue == "➗" { - self.init(baseEmoji: .heavyDivisionSign, skinTones: nil) - } else if rawValue == "🟰" { - self.init(baseEmoji: .heavyEqualsSign, skinTones: nil) - } else if rawValue == "♾️" { - self.init(baseEmoji: .infinity, skinTones: nil) - } else if rawValue == "‼️" { - self.init(baseEmoji: .bangbang, skinTones: nil) - } else if rawValue == "⁉️" { - self.init(baseEmoji: .interrobang, skinTones: nil) - } else if rawValue == "❓" { - self.init(baseEmoji: .question, skinTones: nil) - } else if rawValue == "❔" { - self.init(baseEmoji: .greyQuestion, skinTones: nil) - } else if rawValue == "❕" { - self.init(baseEmoji: .greyExclamation, skinTones: nil) - } else if rawValue == "❗" { - self.init(baseEmoji: .exclamation, skinTones: nil) - } else if rawValue == "〰️" { - self.init(baseEmoji: .wavyDash, skinTones: nil) - } else if rawValue == "💱" { - self.init(baseEmoji: .currencyExchange, skinTones: nil) - } else if rawValue == "💲" { - self.init(baseEmoji: .heavyDollarSign, skinTones: nil) - } else if rawValue == "⚕️" { - self.init(baseEmoji: .medicalSymbol, skinTones: nil) - } else if rawValue == "♻️" { - self.init(baseEmoji: .recycle, skinTones: nil) - } else if rawValue == "⚜️" { - self.init(baseEmoji: .fleurDeLis, skinTones: nil) - } else if rawValue == "🔱" { - self.init(baseEmoji: .trident, skinTones: nil) - } else if rawValue == "📛" { - self.init(baseEmoji: .nameBadge, skinTones: nil) - } else if rawValue == "🔰" { - self.init(baseEmoji: .beginner, skinTones: nil) - } else if rawValue == "⭕" { - self.init(baseEmoji: .o, skinTones: nil) - } else if rawValue == "✅" { - self.init(baseEmoji: .whiteCheckMark, skinTones: nil) - } else if rawValue == "☑️" { - self.init(baseEmoji: .ballotBoxWithCheck, skinTones: nil) - } else if rawValue == "✔️" { - self.init(baseEmoji: .heavyCheckMark, skinTones: nil) - } else if rawValue == "❌" { - self.init(baseEmoji: .x, skinTones: nil) - } else if rawValue == "❎" { - self.init(baseEmoji: .negativeSquaredCrossMark, skinTones: nil) - } else if rawValue == "➰" { - self.init(baseEmoji: .curlyLoop, skinTones: nil) - } else if rawValue == "➿" { - self.init(baseEmoji: .loop, skinTones: nil) - } else if rawValue == "〽️" { - self.init(baseEmoji: .partAlternationMark, skinTones: nil) - } else if rawValue == "✳️" { - self.init(baseEmoji: .eightSpokedAsterisk, skinTones: nil) - } else if rawValue == "✴️" { - self.init(baseEmoji: .eightPointedBlackStar, skinTones: nil) - } else if rawValue == "❇️" { - self.init(baseEmoji: .sparkle, skinTones: nil) - } else if rawValue == "©️" { - self.init(baseEmoji: .copyright, skinTones: nil) - } else if rawValue == "®️" { - self.init(baseEmoji: .registered, skinTones: nil) - } else if rawValue == "™️" { - self.init(baseEmoji: .tm, skinTones: nil) - } else if rawValue == "#️⃣" { - self.init(baseEmoji: .hash, skinTones: nil) - } else if rawValue == "*️⃣" { - self.init(baseEmoji: .keycapStar, skinTones: nil) - } else if rawValue == "0️⃣" { - self.init(baseEmoji: .zero, skinTones: nil) - } else if rawValue == "1️⃣" { - self.init(baseEmoji: .one, skinTones: nil) - } else if rawValue == "2️⃣" { - self.init(baseEmoji: .two, skinTones: nil) - } else if rawValue == "3️⃣" { - self.init(baseEmoji: .three, skinTones: nil) - } else if rawValue == "4️⃣" { - self.init(baseEmoji: .four, skinTones: nil) - } else if rawValue == "5️⃣" { - self.init(baseEmoji: .five, skinTones: nil) - } else if rawValue == "6️⃣" { - self.init(baseEmoji: .six, skinTones: nil) - } else if rawValue == "7️⃣" { - self.init(baseEmoji: .seven, skinTones: nil) - } else if rawValue == "8️⃣" { - self.init(baseEmoji: .eight, skinTones: nil) - } else if rawValue == "9️⃣" { - self.init(baseEmoji: .nine, skinTones: nil) - } else if rawValue == "🔟" { - self.init(baseEmoji: .keycapTen, skinTones: nil) - } else if rawValue == "🔠" { - self.init(baseEmoji: .capitalAbcd, skinTones: nil) - } else if rawValue == "🔡" { - self.init(baseEmoji: .abcd, skinTones: nil) - } else if rawValue == "🔢" { - self.init(baseEmoji: .oneTwoThreeFour, skinTones: nil) - } else if rawValue == "🔣" { - self.init(baseEmoji: .symbols, skinTones: nil) - } else if rawValue == "🔤" { - self.init(baseEmoji: .abc, skinTones: nil) - } else if rawValue == "🅰️" { - self.init(baseEmoji: .a, skinTones: nil) - } else if rawValue == "🆎" { - self.init(baseEmoji: .ab, skinTones: nil) - } else if rawValue == "🅱️" { - self.init(baseEmoji: .b, skinTones: nil) - } else if rawValue == "🆑" { - self.init(baseEmoji: .cl, skinTones: nil) - } else if rawValue == "🆒" { - self.init(baseEmoji: .cool, skinTones: nil) - } else if rawValue == "🆓" { - self.init(baseEmoji: .free, skinTones: nil) - } else if rawValue == "ℹ️" { - self.init(baseEmoji: .informationSource, skinTones: nil) - } else if rawValue == "🆔" { - self.init(baseEmoji: .id, skinTones: nil) - } else if rawValue == "Ⓜ️" { - self.init(baseEmoji: .m, skinTones: nil) - } else if rawValue == "🆕" { - self.init(baseEmoji: .new, skinTones: nil) - } else if rawValue == "🆖" { - self.init(baseEmoji: .ng, skinTones: nil) - } else if rawValue == "🅾️" { - self.init(baseEmoji: .o2, skinTones: nil) - } else if rawValue == "🆗" { - self.init(baseEmoji: .ok, skinTones: nil) - } else if rawValue == "🅿️" { - self.init(baseEmoji: .parking, skinTones: nil) - } else if rawValue == "🆘" { - self.init(baseEmoji: .sos, skinTones: nil) - } else if rawValue == "🆙" { - self.init(baseEmoji: .up, skinTones: nil) - } else if rawValue == "🆚" { - self.init(baseEmoji: .vs, skinTones: nil) - } else if rawValue == "🈁" { - self.init(baseEmoji: .koko, skinTones: nil) - } else if rawValue == "🈂️" { - self.init(baseEmoji: .sa, skinTones: nil) - } else if rawValue == "🈷️" { - self.init(baseEmoji: .u6708, skinTones: nil) - } else if rawValue == "🈶" { - self.init(baseEmoji: .u6709, skinTones: nil) - } else if rawValue == "🈯" { - self.init(baseEmoji: .u6307, skinTones: nil) - } else if rawValue == "🉐" { - self.init(baseEmoji: .ideographAdvantage, skinTones: nil) - } else if rawValue == "🈹" { - self.init(baseEmoji: .u5272, skinTones: nil) - } else if rawValue == "🈚" { - self.init(baseEmoji: .u7121, skinTones: nil) - } else if rawValue == "🈲" { - self.init(baseEmoji: .u7981, skinTones: nil) - } else if rawValue == "🉑" { - self.init(baseEmoji: .accept, skinTones: nil) - } else if rawValue == "🈸" { - self.init(baseEmoji: .u7533, skinTones: nil) - } else if rawValue == "🈴" { - self.init(baseEmoji: .u5408, skinTones: nil) - } else if rawValue == "🈳" { - self.init(baseEmoji: .u7a7a, skinTones: nil) - } else if rawValue == "㊗️" { - self.init(baseEmoji: .congratulations, skinTones: nil) - } else if rawValue == "㊙️" { - self.init(baseEmoji: .secret, skinTones: nil) - } else if rawValue == "🈺" { - self.init(baseEmoji: .u55b6, skinTones: nil) - } else if rawValue == "🈵" { - self.init(baseEmoji: .u6e80, skinTones: nil) - } else if rawValue == "🔴" { - self.init(baseEmoji: .redCircle, skinTones: nil) - } else if rawValue == "🟠" { - self.init(baseEmoji: .largeOrangeCircle, skinTones: nil) - } else if rawValue == "🟡" { - self.init(baseEmoji: .largeYellowCircle, skinTones: nil) - } else if rawValue == "🟢" { - self.init(baseEmoji: .largeGreenCircle, skinTones: nil) - } else if rawValue == "🔵" { - self.init(baseEmoji: .largeBlueCircle, skinTones: nil) - } else if rawValue == "🟣" { - self.init(baseEmoji: .largePurpleCircle, skinTones: nil) - } else if rawValue == "🟤" { - self.init(baseEmoji: .largeBrownCircle, skinTones: nil) - } else if rawValue == "⚫" { - self.init(baseEmoji: .blackCircle, skinTones: nil) - } else if rawValue == "⚪" { - self.init(baseEmoji: .whiteCircle, skinTones: nil) - } else if rawValue == "🟥" { - self.init(baseEmoji: .largeRedSquare, skinTones: nil) - } else if rawValue == "🟧" { - self.init(baseEmoji: .largeOrangeSquare, skinTones: nil) - } else if rawValue == "🟨" { - self.init(baseEmoji: .largeYellowSquare, skinTones: nil) - } else if rawValue == "🟩" { - self.init(baseEmoji: .largeGreenSquare, skinTones: nil) - } else if rawValue == "🟦" { - self.init(baseEmoji: .largeBlueSquare, skinTones: nil) - } else if rawValue == "🟪" { - self.init(baseEmoji: .largePurpleSquare, skinTones: nil) - } else if rawValue == "🟫" { - self.init(baseEmoji: .largeBrownSquare, skinTones: nil) - } else if rawValue == "⬛" { - self.init(baseEmoji: .blackLargeSquare, skinTones: nil) - } else if rawValue == "⬜" { - self.init(baseEmoji: .whiteLargeSquare, skinTones: nil) - } else if rawValue == "◼️" { - self.init(baseEmoji: .blackMediumSquare, skinTones: nil) - } else if rawValue == "◻️" { - self.init(baseEmoji: .whiteMediumSquare, skinTones: nil) - } else if rawValue == "◾" { - self.init(baseEmoji: .blackMediumSmallSquare, skinTones: nil) - } else if rawValue == "◽" { - self.init(baseEmoji: .whiteMediumSmallSquare, skinTones: nil) - } else if rawValue == "▪️" { - self.init(baseEmoji: .blackSmallSquare, skinTones: nil) - } else if rawValue == "▫️" { - self.init(baseEmoji: .whiteSmallSquare, skinTones: nil) - } else if rawValue == "🔶" { - self.init(baseEmoji: .largeOrangeDiamond, skinTones: nil) - } else if rawValue == "🔷" { - self.init(baseEmoji: .largeBlueDiamond, skinTones: nil) - } else if rawValue == "🔸" { - self.init(baseEmoji: .smallOrangeDiamond, skinTones: nil) - } else if rawValue == "🔹" { - self.init(baseEmoji: .smallBlueDiamond, skinTones: nil) - } else if rawValue == "🔺" { - self.init(baseEmoji: .smallRedTriangle, skinTones: nil) - } else if rawValue == "🔻" { - self.init(baseEmoji: .smallRedTriangleDown, skinTones: nil) - } else if rawValue == "💠" { - self.init(baseEmoji: .diamondShapeWithADotInside, skinTones: nil) - } else if rawValue == "🔘" { - self.init(baseEmoji: .radioButton, skinTones: nil) - } else if rawValue == "🔳" { - self.init(baseEmoji: .whiteSquareButton, skinTones: nil) - } else if rawValue == "🔲" { - self.init(baseEmoji: .blackSquareButton, skinTones: nil) - } else if rawValue == "🏁" { - self.init(baseEmoji: .checkeredFlag, skinTones: nil) - } else if rawValue == "🚩" { - self.init(baseEmoji: .triangularFlagOnPost, skinTones: nil) - } else if rawValue == "🎌" { - self.init(baseEmoji: .crossedFlags, skinTones: nil) - } else if rawValue == "🏴" { - self.init(baseEmoji: .wavingBlackFlag, skinTones: nil) - } else if rawValue == "🏳️" { - self.init(baseEmoji: .wavingWhiteFlag, skinTones: nil) - } else if rawValue == "🏳️‍🌈" { - self.init(baseEmoji: .rainbowFlag, skinTones: nil) - } else if rawValue == "🏳️‍⚧️" { - self.init(baseEmoji: .transgenderFlag, skinTones: nil) - } else if rawValue == "🏴‍☠️" { - self.init(baseEmoji: .pirateFlag, skinTones: nil) - } else if rawValue == "🇦🇨" { - self.init(baseEmoji: .flagAc, skinTones: nil) - } else if rawValue == "🇦🇩" { - self.init(baseEmoji: .flagAd, skinTones: nil) - } else if rawValue == "🇦🇪" { - self.init(baseEmoji: .flagAe, skinTones: nil) - } else if rawValue == "🇦🇫" { - self.init(baseEmoji: .flagAf, skinTones: nil) - } else if rawValue == "🇦🇬" { - self.init(baseEmoji: .flagAg, skinTones: nil) - } else if rawValue == "🇦🇮" { - self.init(baseEmoji: .flagAi, skinTones: nil) - } else if rawValue == "🇦🇱" { - self.init(baseEmoji: .flagAl, skinTones: nil) - } else if rawValue == "🇦🇲" { - self.init(baseEmoji: .flagAm, skinTones: nil) - } else if rawValue == "🇦🇴" { - self.init(baseEmoji: .flagAo, skinTones: nil) - } else if rawValue == "🇦🇶" { - self.init(baseEmoji: .flagAq, skinTones: nil) - } else if rawValue == "🇦🇷" { - self.init(baseEmoji: .flagAr, skinTones: nil) - } else if rawValue == "🇦🇸" { - self.init(baseEmoji: .flagAs, skinTones: nil) - } else if rawValue == "🇦🇹" { - self.init(baseEmoji: .flagAt, skinTones: nil) - } else if rawValue == "🇦🇺" { - self.init(baseEmoji: .flagAu, skinTones: nil) - } else if rawValue == "🇦🇼" { - self.init(baseEmoji: .flagAw, skinTones: nil) - } else if rawValue == "🇦🇽" { - self.init(baseEmoji: .flagAx, skinTones: nil) - } else if rawValue == "🇦🇿" { - self.init(baseEmoji: .flagAz, skinTones: nil) - } else if rawValue == "🇧🇦" { - self.init(baseEmoji: .flagBa, skinTones: nil) - } else if rawValue == "🇧🇧" { - self.init(baseEmoji: .flagBb, skinTones: nil) - } else if rawValue == "🇧🇩" { - self.init(baseEmoji: .flagBd, skinTones: nil) - } else if rawValue == "🇧🇪" { - self.init(baseEmoji: .flagBe, skinTones: nil) - } else if rawValue == "🇧🇫" { - self.init(baseEmoji: .flagBf, skinTones: nil) - } else if rawValue == "🇧🇬" { - self.init(baseEmoji: .flagBg, skinTones: nil) - } else if rawValue == "🇧🇭" { - self.init(baseEmoji: .flagBh, skinTones: nil) - } else if rawValue == "🇧🇮" { - self.init(baseEmoji: .flagBi, skinTones: nil) - } else if rawValue == "🇧🇯" { - self.init(baseEmoji: .flagBj, skinTones: nil) - } else if rawValue == "🇧🇱" { - self.init(baseEmoji: .flagBl, skinTones: nil) - } else if rawValue == "🇧🇲" { - self.init(baseEmoji: .flagBm, skinTones: nil) - } else if rawValue == "🇧🇳" { - self.init(baseEmoji: .flagBn, skinTones: nil) - } else if rawValue == "🇧🇴" { - self.init(baseEmoji: .flagBo, skinTones: nil) - } else if rawValue == "🇧🇶" { - self.init(baseEmoji: .flagBq, skinTones: nil) - } else if rawValue == "🇧🇷" { - self.init(baseEmoji: .flagBr, skinTones: nil) - } else if rawValue == "🇧🇸" { - self.init(baseEmoji: .flagBs, skinTones: nil) - } else if rawValue == "🇧🇹" { - self.init(baseEmoji: .flagBt, skinTones: nil) - } else if rawValue == "🇧🇻" { - self.init(baseEmoji: .flagBv, skinTones: nil) - } else if rawValue == "🇧🇼" { - self.init(baseEmoji: .flagBw, skinTones: nil) - } else if rawValue == "🇧🇾" { - self.init(baseEmoji: .flagBy, skinTones: nil) - } else if rawValue == "🇧🇿" { - self.init(baseEmoji: .flagBz, skinTones: nil) - } else if rawValue == "🇨🇦" { - self.init(baseEmoji: .flagCa, skinTones: nil) - } else if rawValue == "🇨🇨" { - self.init(baseEmoji: .flagCc, skinTones: nil) - } else if rawValue == "🇨🇩" { - self.init(baseEmoji: .flagCd, skinTones: nil) - } else if rawValue == "🇨🇫" { - self.init(baseEmoji: .flagCf, skinTones: nil) - } else if rawValue == "🇨🇬" { - self.init(baseEmoji: .flagCg, skinTones: nil) - } else if rawValue == "🇨🇭" { - self.init(baseEmoji: .flagCh, skinTones: nil) - } else if rawValue == "🇨🇮" { - self.init(baseEmoji: .flagCi, skinTones: nil) - } else if rawValue == "🇨🇰" { - self.init(baseEmoji: .flagCk, skinTones: nil) - } else if rawValue == "🇨🇱" { - self.init(baseEmoji: .flagCl, skinTones: nil) - } else if rawValue == "🇨🇲" { - self.init(baseEmoji: .flagCm, skinTones: nil) - } else if rawValue == "🇨🇳" { - self.init(baseEmoji: .cn, skinTones: nil) - } else if rawValue == "🇨🇴" { - self.init(baseEmoji: .flagCo, skinTones: nil) - } else if rawValue == "🇨🇵" { - self.init(baseEmoji: .flagCp, skinTones: nil) - } else if rawValue == "🇨🇷" { - self.init(baseEmoji: .flagCr, skinTones: nil) - } else if rawValue == "🇨🇺" { - self.init(baseEmoji: .flagCu, skinTones: nil) - } else if rawValue == "🇨🇻" { - self.init(baseEmoji: .flagCv, skinTones: nil) - } else if rawValue == "🇨🇼" { - self.init(baseEmoji: .flagCw, skinTones: nil) - } else if rawValue == "🇨🇽" { - self.init(baseEmoji: .flagCx, skinTones: nil) - } else if rawValue == "🇨🇾" { - self.init(baseEmoji: .flagCy, skinTones: nil) - } else if rawValue == "🇨🇿" { - self.init(baseEmoji: .flagCz, skinTones: nil) - } else if rawValue == "🇩🇪" { - self.init(baseEmoji: .de, skinTones: nil) - } else if rawValue == "🇩🇬" { - self.init(baseEmoji: .flagDg, skinTones: nil) - } else if rawValue == "🇩🇯" { - self.init(baseEmoji: .flagDj, skinTones: nil) - } else if rawValue == "🇩🇰" { - self.init(baseEmoji: .flagDk, skinTones: nil) - } else if rawValue == "🇩🇲" { - self.init(baseEmoji: .flagDm, skinTones: nil) - } else if rawValue == "🇩🇴" { - self.init(baseEmoji: .flagDo, skinTones: nil) - } else if rawValue == "🇩🇿" { - self.init(baseEmoji: .flagDz, skinTones: nil) - } else if rawValue == "🇪🇦" { - self.init(baseEmoji: .flagEa, skinTones: nil) - } else if rawValue == "🇪🇨" { - self.init(baseEmoji: .flagEc, skinTones: nil) - } else if rawValue == "🇪🇪" { - self.init(baseEmoji: .flagEe, skinTones: nil) - } else if rawValue == "🇪🇬" { - self.init(baseEmoji: .flagEg, skinTones: nil) - } else if rawValue == "🇪🇭" { - self.init(baseEmoji: .flagEh, skinTones: nil) - } else if rawValue == "🇪🇷" { - self.init(baseEmoji: .flagEr, skinTones: nil) - } else if rawValue == "🇪🇸" { - self.init(baseEmoji: .es, skinTones: nil) - } else if rawValue == "🇪🇹" { - self.init(baseEmoji: .flagEt, skinTones: nil) - } else if rawValue == "🇪🇺" { - self.init(baseEmoji: .flagEu, skinTones: nil) - } else if rawValue == "🇫🇮" { - self.init(baseEmoji: .flagFi, skinTones: nil) - } else if rawValue == "🇫🇯" { - self.init(baseEmoji: .flagFj, skinTones: nil) - } else if rawValue == "🇫🇰" { - self.init(baseEmoji: .flagFk, skinTones: nil) - } else if rawValue == "🇫🇲" { - self.init(baseEmoji: .flagFm, skinTones: nil) - } else if rawValue == "🇫🇴" { - self.init(baseEmoji: .flagFo, skinTones: nil) - } else if rawValue == "🇫🇷" { - self.init(baseEmoji: .fr, skinTones: nil) - } else if rawValue == "🇬🇦" { - self.init(baseEmoji: .flagGa, skinTones: nil) - } else if rawValue == "🇬🇧" { - self.init(baseEmoji: .gb, skinTones: nil) - } else if rawValue == "🇬🇩" { - self.init(baseEmoji: .flagGd, skinTones: nil) - } else if rawValue == "🇬🇪" { - self.init(baseEmoji: .flagGe, skinTones: nil) - } else if rawValue == "🇬🇫" { - self.init(baseEmoji: .flagGf, skinTones: nil) - } else if rawValue == "🇬🇬" { - self.init(baseEmoji: .flagGg, skinTones: nil) - } else if rawValue == "🇬🇭" { - self.init(baseEmoji: .flagGh, skinTones: nil) - } else if rawValue == "🇬🇮" { - self.init(baseEmoji: .flagGi, skinTones: nil) - } else if rawValue == "🇬🇱" { - self.init(baseEmoji: .flagGl, skinTones: nil) - } else if rawValue == "🇬🇲" { - self.init(baseEmoji: .flagGm, skinTones: nil) - } else if rawValue == "🇬🇳" { - self.init(baseEmoji: .flagGn, skinTones: nil) - } else if rawValue == "🇬🇵" { - self.init(baseEmoji: .flagGp, skinTones: nil) - } else if rawValue == "🇬🇶" { - self.init(baseEmoji: .flagGq, skinTones: nil) - } else if rawValue == "🇬🇷" { - self.init(baseEmoji: .flagGr, skinTones: nil) - } else if rawValue == "🇬🇸" { - self.init(baseEmoji: .flagGs, skinTones: nil) - } else if rawValue == "🇬🇹" { - self.init(baseEmoji: .flagGt, skinTones: nil) - } else if rawValue == "🇬🇺" { - self.init(baseEmoji: .flagGu, skinTones: nil) - } else if rawValue == "🇬🇼" { - self.init(baseEmoji: .flagGw, skinTones: nil) - } else if rawValue == "🇬🇾" { - self.init(baseEmoji: .flagGy, skinTones: nil) - } else if rawValue == "🇭🇰" { - self.init(baseEmoji: .flagHk, skinTones: nil) - } else if rawValue == "🇭🇲" { - self.init(baseEmoji: .flagHm, skinTones: nil) - } else if rawValue == "🇭🇳" { - self.init(baseEmoji: .flagHn, skinTones: nil) - } else if rawValue == "🇭🇷" { - self.init(baseEmoji: .flagHr, skinTones: nil) - } else if rawValue == "🇭🇹" { - self.init(baseEmoji: .flagHt, skinTones: nil) - } else if rawValue == "🇭🇺" { - self.init(baseEmoji: .flagHu, skinTones: nil) - } else if rawValue == "🇮🇨" { - self.init(baseEmoji: .flagIc, skinTones: nil) - } else if rawValue == "🇮🇩" { - self.init(baseEmoji: .flagId, skinTones: nil) - } else if rawValue == "🇮🇪" { - self.init(baseEmoji: .flagIe, skinTones: nil) - } else if rawValue == "🇮🇱" { - self.init(baseEmoji: .flagIl, skinTones: nil) - } else if rawValue == "🇮🇲" { - self.init(baseEmoji: .flagIm, skinTones: nil) - } else if rawValue == "🇮🇳" { - self.init(baseEmoji: .flagIn, skinTones: nil) - } else if rawValue == "🇮🇴" { - self.init(baseEmoji: .flagIo, skinTones: nil) - } else if rawValue == "🇮🇶" { - self.init(baseEmoji: .flagIq, skinTones: nil) - } else if rawValue == "🇮🇷" { - self.init(baseEmoji: .flagIr, skinTones: nil) - } else if rawValue == "🇮🇸" { - self.init(baseEmoji: .flagIs, skinTones: nil) - } else if rawValue == "🇮🇹" { - self.init(baseEmoji: .it, skinTones: nil) - } else if rawValue == "🇯🇪" { - self.init(baseEmoji: .flagJe, skinTones: nil) - } else if rawValue == "🇯🇲" { - self.init(baseEmoji: .flagJm, skinTones: nil) - } else if rawValue == "🇯🇴" { - self.init(baseEmoji: .flagJo, skinTones: nil) - } else if rawValue == "🇯🇵" { - self.init(baseEmoji: .jp, skinTones: nil) - } else if rawValue == "🇰🇪" { - self.init(baseEmoji: .flagKe, skinTones: nil) - } else if rawValue == "🇰🇬" { - self.init(baseEmoji: .flagKg, skinTones: nil) - } else if rawValue == "🇰🇭" { - self.init(baseEmoji: .flagKh, skinTones: nil) - } else if rawValue == "🇰🇮" { - self.init(baseEmoji: .flagKi, skinTones: nil) - } else if rawValue == "🇰🇲" { - self.init(baseEmoji: .flagKm, skinTones: nil) - } else if rawValue == "🇰🇳" { - self.init(baseEmoji: .flagKn, skinTones: nil) - } else if rawValue == "🇰🇵" { - self.init(baseEmoji: .flagKp, skinTones: nil) - } else if rawValue == "🇰🇷" { - self.init(baseEmoji: .kr, skinTones: nil) - } else if rawValue == "🇰🇼" { - self.init(baseEmoji: .flagKw, skinTones: nil) - } else if rawValue == "🇰🇾" { - self.init(baseEmoji: .flagKy, skinTones: nil) - } else if rawValue == "🇰🇿" { - self.init(baseEmoji: .flagKz, skinTones: nil) - } else if rawValue == "🇱🇦" { - self.init(baseEmoji: .flagLa, skinTones: nil) - } else if rawValue == "🇱🇧" { - self.init(baseEmoji: .flagLb, skinTones: nil) - } else if rawValue == "🇱🇨" { - self.init(baseEmoji: .flagLc, skinTones: nil) - } else if rawValue == "🇱🇮" { - self.init(baseEmoji: .flagLi, skinTones: nil) - } else if rawValue == "🇱🇰" { - self.init(baseEmoji: .flagLk, skinTones: nil) - } else if rawValue == "🇱🇷" { - self.init(baseEmoji: .flagLr, skinTones: nil) - } else if rawValue == "🇱🇸" { - self.init(baseEmoji: .flagLs, skinTones: nil) - } else if rawValue == "🇱🇹" { - self.init(baseEmoji: .flagLt, skinTones: nil) - } else if rawValue == "🇱🇺" { - self.init(baseEmoji: .flagLu, skinTones: nil) - } else if rawValue == "🇱🇻" { - self.init(baseEmoji: .flagLv, skinTones: nil) - } else if rawValue == "🇱🇾" { - self.init(baseEmoji: .flagLy, skinTones: nil) - } else if rawValue == "🇲🇦" { - self.init(baseEmoji: .flagMa, skinTones: nil) - } else if rawValue == "🇲🇨" { - self.init(baseEmoji: .flagMc, skinTones: nil) - } else if rawValue == "🇲🇩" { - self.init(baseEmoji: .flagMd, skinTones: nil) - } else if rawValue == "🇲🇪" { - self.init(baseEmoji: .flagMe, skinTones: nil) - } else if rawValue == "🇲🇫" { - self.init(baseEmoji: .flagMf, skinTones: nil) - } else if rawValue == "🇲🇬" { - self.init(baseEmoji: .flagMg, skinTones: nil) - } else if rawValue == "🇲🇭" { - self.init(baseEmoji: .flagMh, skinTones: nil) - } else if rawValue == "🇲🇰" { - self.init(baseEmoji: .flagMk, skinTones: nil) - } else if rawValue == "🇲🇱" { - self.init(baseEmoji: .flagMl, skinTones: nil) - } else if rawValue == "🇲🇲" { - self.init(baseEmoji: .flagMm, skinTones: nil) - } else if rawValue == "🇲🇳" { - self.init(baseEmoji: .flagMn, skinTones: nil) - } else if rawValue == "🇲🇴" { - self.init(baseEmoji: .flagMo, skinTones: nil) - } else if rawValue == "🇲🇵" { - self.init(baseEmoji: .flagMp, skinTones: nil) - } else if rawValue == "🇲🇶" { - self.init(baseEmoji: .flagMq, skinTones: nil) - } else if rawValue == "🇲🇷" { - self.init(baseEmoji: .flagMr, skinTones: nil) - } else if rawValue == "🇲🇸" { - self.init(baseEmoji: .flagMs, skinTones: nil) - } else if rawValue == "🇲🇹" { - self.init(baseEmoji: .flagMt, skinTones: nil) - } else if rawValue == "🇲🇺" { - self.init(baseEmoji: .flagMu, skinTones: nil) - } else if rawValue == "🇲🇻" { - self.init(baseEmoji: .flagMv, skinTones: nil) - } else if rawValue == "🇲🇼" { - self.init(baseEmoji: .flagMw, skinTones: nil) - } else if rawValue == "🇲🇽" { - self.init(baseEmoji: .flagMx, skinTones: nil) - } else if rawValue == "🇲🇾" { - self.init(baseEmoji: .flagMy, skinTones: nil) - } else if rawValue == "🇲🇿" { - self.init(baseEmoji: .flagMz, skinTones: nil) - } else if rawValue == "🇳🇦" { - self.init(baseEmoji: .flagNa, skinTones: nil) - } else if rawValue == "🇳🇨" { - self.init(baseEmoji: .flagNc, skinTones: nil) - } else if rawValue == "🇳🇪" { - self.init(baseEmoji: .flagNe, skinTones: nil) - } else if rawValue == "🇳🇫" { - self.init(baseEmoji: .flagNf, skinTones: nil) - } else if rawValue == "🇳🇬" { - self.init(baseEmoji: .flagNg, skinTones: nil) - } else if rawValue == "🇳🇮" { - self.init(baseEmoji: .flagNi, skinTones: nil) - } else if rawValue == "🇳🇱" { - self.init(baseEmoji: .flagNl, skinTones: nil) - } else if rawValue == "🇳🇴" { - self.init(baseEmoji: .flagNo, skinTones: nil) - } else if rawValue == "🇳🇵" { - self.init(baseEmoji: .flagNp, skinTones: nil) - } else if rawValue == "🇳🇷" { - self.init(baseEmoji: .flagNr, skinTones: nil) - } else if rawValue == "🇳🇺" { - self.init(baseEmoji: .flagNu, skinTones: nil) - } else if rawValue == "🇳🇿" { - self.init(baseEmoji: .flagNz, skinTones: nil) - } else if rawValue == "🇴🇲" { - self.init(baseEmoji: .flagOm, skinTones: nil) - } else if rawValue == "🇵🇦" { - self.init(baseEmoji: .flagPa, skinTones: nil) - } else if rawValue == "🇵🇪" { - self.init(baseEmoji: .flagPe, skinTones: nil) - } else if rawValue == "🇵🇫" { - self.init(baseEmoji: .flagPf, skinTones: nil) - } else if rawValue == "🇵🇬" { - self.init(baseEmoji: .flagPg, skinTones: nil) - } else if rawValue == "🇵🇭" { - self.init(baseEmoji: .flagPh, skinTones: nil) - } else if rawValue == "🇵🇰" { - self.init(baseEmoji: .flagPk, skinTones: nil) - } else if rawValue == "🇵🇱" { - self.init(baseEmoji: .flagPl, skinTones: nil) - } else if rawValue == "🇵🇲" { - self.init(baseEmoji: .flagPm, skinTones: nil) - } else if rawValue == "🇵🇳" { - self.init(baseEmoji: .flagPn, skinTones: nil) - } else if rawValue == "🇵🇷" { - self.init(baseEmoji: .flagPr, skinTones: nil) - } else if rawValue == "🇵🇸" { - self.init(baseEmoji: .flagPs, skinTones: nil) - } else if rawValue == "🇵🇹" { - self.init(baseEmoji: .flagPt, skinTones: nil) - } else if rawValue == "🇵🇼" { - self.init(baseEmoji: .flagPw, skinTones: nil) - } else if rawValue == "🇵🇾" { - self.init(baseEmoji: .flagPy, skinTones: nil) - } else if rawValue == "🇶🇦" { - self.init(baseEmoji: .flagQa, skinTones: nil) - } else if rawValue == "🇷🇪" { - self.init(baseEmoji: .flagRe, skinTones: nil) - } else if rawValue == "🇷🇴" { - self.init(baseEmoji: .flagRo, skinTones: nil) - } else if rawValue == "🇷🇸" { - self.init(baseEmoji: .flagRs, skinTones: nil) - } else if rawValue == "🇷🇺" { - self.init(baseEmoji: .ru, skinTones: nil) - } else if rawValue == "🇷🇼" { - self.init(baseEmoji: .flagRw, skinTones: nil) - } else if rawValue == "🇸🇦" { - self.init(baseEmoji: .flagSa, skinTones: nil) - } else if rawValue == "🇸🇧" { - self.init(baseEmoji: .flagSb, skinTones: nil) - } else if rawValue == "🇸🇨" { - self.init(baseEmoji: .flagSc, skinTones: nil) - } else if rawValue == "🇸🇩" { - self.init(baseEmoji: .flagSd, skinTones: nil) - } else if rawValue == "🇸🇪" { - self.init(baseEmoji: .flagSe, skinTones: nil) - } else if rawValue == "🇸🇬" { - self.init(baseEmoji: .flagSg, skinTones: nil) - } else if rawValue == "🇸🇭" { - self.init(baseEmoji: .flagSh, skinTones: nil) - } else if rawValue == "🇸🇮" { - self.init(baseEmoji: .flagSi, skinTones: nil) - } else if rawValue == "🇸🇯" { - self.init(baseEmoji: .flagSj, skinTones: nil) - } else if rawValue == "🇸🇰" { - self.init(baseEmoji: .flagSk, skinTones: nil) - } else if rawValue == "🇸🇱" { - self.init(baseEmoji: .flagSl, skinTones: nil) - } else if rawValue == "🇸🇲" { - self.init(baseEmoji: .flagSm, skinTones: nil) - } else if rawValue == "🇸🇳" { - self.init(baseEmoji: .flagSn, skinTones: nil) - } else if rawValue == "🇸🇴" { - self.init(baseEmoji: .flagSo, skinTones: nil) - } else if rawValue == "🇸🇷" { - self.init(baseEmoji: .flagSr, skinTones: nil) - } else if rawValue == "🇸🇸" { - self.init(baseEmoji: .flagSs, skinTones: nil) - } else if rawValue == "🇸🇹" { - self.init(baseEmoji: .flagSt, skinTones: nil) - } else if rawValue == "🇸🇻" { - self.init(baseEmoji: .flagSv, skinTones: nil) - } else if rawValue == "🇸🇽" { - self.init(baseEmoji: .flagSx, skinTones: nil) - } else if rawValue == "🇸🇾" { - self.init(baseEmoji: .flagSy, skinTones: nil) - } else if rawValue == "🇸🇿" { - self.init(baseEmoji: .flagSz, skinTones: nil) - } else if rawValue == "🇹🇦" { - self.init(baseEmoji: .flagTa, skinTones: nil) - } else if rawValue == "🇹🇨" { - self.init(baseEmoji: .flagTc, skinTones: nil) - } else if rawValue == "🇹🇩" { - self.init(baseEmoji: .flagTd, skinTones: nil) - } else if rawValue == "🇹🇫" { - self.init(baseEmoji: .flagTf, skinTones: nil) - } else if rawValue == "🇹🇬" { - self.init(baseEmoji: .flagTg, skinTones: nil) - } else if rawValue == "🇹🇭" { - self.init(baseEmoji: .flagTh, skinTones: nil) - } else if rawValue == "🇹🇯" { - self.init(baseEmoji: .flagTj, skinTones: nil) - } else if rawValue == "🇹🇰" { - self.init(baseEmoji: .flagTk, skinTones: nil) - } else if rawValue == "🇹🇱" { - self.init(baseEmoji: .flagTl, skinTones: nil) - } else if rawValue == "🇹🇲" { - self.init(baseEmoji: .flagTm, skinTones: nil) - } else if rawValue == "🇹🇳" { - self.init(baseEmoji: .flagTn, skinTones: nil) - } else if rawValue == "🇹🇴" { - self.init(baseEmoji: .flagTo, skinTones: nil) - } else if rawValue == "🇹🇷" { - self.init(baseEmoji: .flagTr, skinTones: nil) - } else if rawValue == "🇹🇹" { - self.init(baseEmoji: .flagTt, skinTones: nil) - } else if rawValue == "🇹🇻" { - self.init(baseEmoji: .flagTv, skinTones: nil) - } else if rawValue == "🇹🇼" { - self.init(baseEmoji: .flagTw, skinTones: nil) - } else if rawValue == "🇹🇿" { - self.init(baseEmoji: .flagTz, skinTones: nil) - } else if rawValue == "🇺🇦" { - self.init(baseEmoji: .flagUa, skinTones: nil) - } else if rawValue == "🇺🇬" { - self.init(baseEmoji: .flagUg, skinTones: nil) - } else if rawValue == "🇺🇲" { - self.init(baseEmoji: .flagUm, skinTones: nil) - } else if rawValue == "🇺🇳" { - self.init(baseEmoji: .flagUn, skinTones: nil) - } else if rawValue == "🇺🇸" { - self.init(baseEmoji: .us, skinTones: nil) - } else if rawValue == "🇺🇾" { - self.init(baseEmoji: .flagUy, skinTones: nil) - } else if rawValue == "🇺🇿" { - self.init(baseEmoji: .flagUz, skinTones: nil) - } else if rawValue == "🇻🇦" { - self.init(baseEmoji: .flagVa, skinTones: nil) - } else if rawValue == "🇻🇨" { - self.init(baseEmoji: .flagVc, skinTones: nil) - } else if rawValue == "🇻🇪" { - self.init(baseEmoji: .flagVe, skinTones: nil) - } else if rawValue == "🇻🇬" { - self.init(baseEmoji: .flagVg, skinTones: nil) - } else if rawValue == "🇻🇮" { - self.init(baseEmoji: .flagVi, skinTones: nil) - } else if rawValue == "🇻🇳" { - self.init(baseEmoji: .flagVn, skinTones: nil) - } else if rawValue == "🇻🇺" { - self.init(baseEmoji: .flagVu, skinTones: nil) - } else if rawValue == "🇼🇫" { - self.init(baseEmoji: .flagWf, skinTones: nil) - } else if rawValue == "🇼🇸" { - self.init(baseEmoji: .flagWs, skinTones: nil) - } else if rawValue == "🇽🇰" { - self.init(baseEmoji: .flagXk, skinTones: nil) - } else if rawValue == "🇾🇪" { - self.init(baseEmoji: .flagYe, skinTones: nil) - } else if rawValue == "🇾🇹" { - self.init(baseEmoji: .flagYt, skinTones: nil) - } else if rawValue == "🇿🇦" { - self.init(baseEmoji: .flagZa, skinTones: nil) - } else if rawValue == "🇿🇲" { - self.init(baseEmoji: .flagZm, skinTones: nil) - } else if rawValue == "🇿🇼" { - self.init(baseEmoji: .flagZw, skinTones: nil) - } else if rawValue == "🏴󠁧󠁢󠁥󠁮󠁧󠁿" { - self.init(baseEmoji: .flagEngland, skinTones: nil) - } else if rawValue == "🏴󠁧󠁢󠁳󠁣󠁴󠁿" { - self.init(baseEmoji: .flagScotland, skinTones: nil) - } else if rawValue == "🏴󠁧󠁢󠁷󠁬󠁳󠁿" { - self.init(baseEmoji: .flagWales, skinTones: nil) - } else { - self.init(unsupportedValue: rawValue) + switch rawValue.unicodeScalars.map({ $0.value }).reduce(0, +) { + case 89: self = EmojiWithSkinTones.emojiFrom89(rawValue) + case 91: self = EmojiWithSkinTones.emojiFrom91(rawValue) + case 92: self = EmojiWithSkinTones.emojiFrom92(rawValue) + case 97: self = EmojiWithSkinTones.emojiFrom97(rawValue) + case 98: self = EmojiWithSkinTones.emojiFrom98(rawValue) + case 99: self = EmojiWithSkinTones.emojiFrom99(rawValue) + case 100: self = EmojiWithSkinTones.emojiFrom100(rawValue) + case 101: self = EmojiWithSkinTones.emojiFrom101(rawValue) + case 110: self = EmojiWithSkinTones.emojiFrom110(rawValue) + case 652: self = EmojiWithSkinTones.emojiFrom652(rawValue) + case 732: self = EmojiWithSkinTones.emojiFrom732(rawValue) + case 733: self = EmojiWithSkinTones.emojiFrom733(rawValue) + case 734: self = EmojiWithSkinTones.emojiFrom734(rawValue) + case 735: self = EmojiWithSkinTones.emojiFrom735(rawValue) + case 736: self = EmojiWithSkinTones.emojiFrom736(rawValue) + case 740: self = EmojiWithSkinTones.emojiFrom740(rawValue) + case 742: self = EmojiWithSkinTones.emojiFrom742(rawValue) + case 744: self = EmojiWithSkinTones.emojiFrom744(rawValue) + case 746: self = EmojiWithSkinTones.emojiFrom746(rawValue) + case 747: self = EmojiWithSkinTones.emojiFrom747(rawValue) + case 748: self = EmojiWithSkinTones.emojiFrom748(rawValue) + case 749: self = EmojiWithSkinTones.emojiFrom749(rawValue) + case 750: self = EmojiWithSkinTones.emojiFrom750(rawValue) + case 751: self = EmojiWithSkinTones.emojiFrom751(rawValue) + case 755: self = EmojiWithSkinTones.emojiFrom755(rawValue) + case 760: self = EmojiWithSkinTones.emojiFrom760(rawValue) + case 773: self = EmojiWithSkinTones.emojiFrom773(rawValue) + case 779: self = EmojiWithSkinTones.emojiFrom779(rawValue) + case 1269: self = EmojiWithSkinTones.emojiFrom1269(rawValue) + case 1271: self = EmojiWithSkinTones.emojiFrom1271(rawValue) + case 1273: self = EmojiWithSkinTones.emojiFrom1273(rawValue) + case 1274: self = EmojiWithSkinTones.emojiFrom1274(rawValue) + case 1275: self = EmojiWithSkinTones.emojiFrom1275(rawValue) + case 1277: self = EmojiWithSkinTones.emojiFrom1277(rawValue) + case 1278: self = EmojiWithSkinTones.emojiFrom1278(rawValue) + case 1279: self = EmojiWithSkinTones.emojiFrom1279(rawValue) + case 1280: self = EmojiWithSkinTones.emojiFrom1280(rawValue) + case 1281: self = EmojiWithSkinTones.emojiFrom1281(rawValue) + case 1282: self = EmojiWithSkinTones.emojiFrom1282(rawValue) + case 1283: self = EmojiWithSkinTones.emojiFrom1283(rawValue) + case 1284: self = EmojiWithSkinTones.emojiFrom1284(rawValue) + case 1285: self = EmojiWithSkinTones.emojiFrom1285(rawValue) + case 1286: self = EmojiWithSkinTones.emojiFrom1286(rawValue) + case 1287: self = EmojiWithSkinTones.emojiFrom1287(rawValue) + case 1289: self = EmojiWithSkinTones.emojiFrom1289(rawValue) + case 1290: self = EmojiWithSkinTones.emojiFrom1290(rawValue) + case 1292: self = EmojiWithSkinTones.emojiFrom1292(rawValue) + case 1293: self = EmojiWithSkinTones.emojiFrom1293(rawValue) + case 1294: self = EmojiWithSkinTones.emojiFrom1294(rawValue) + case 1295: self = EmojiWithSkinTones.emojiFrom1295(rawValue) + case 1296: self = EmojiWithSkinTones.emojiFrom1296(rawValue) + case 1297: self = EmojiWithSkinTones.emojiFrom1297(rawValue) + case 1377: self = EmojiWithSkinTones.emojiFrom1377(rawValue) + case 1379: self = EmojiWithSkinTones.emojiFrom1379(rawValue) + case 1472: self = EmojiWithSkinTones.emojiFrom1472(rawValue) + case 1580: self = EmojiWithSkinTones.emojiFrom1580(rawValue) + case 1923: self = EmojiWithSkinTones.emojiFrom1923(rawValue) + case 1925: self = EmojiWithSkinTones.emojiFrom1925(rawValue) + case 1928: self = EmojiWithSkinTones.emojiFrom1928(rawValue) + case 1929: self = EmojiWithSkinTones.emojiFrom1929(rawValue) + case 1930: self = EmojiWithSkinTones.emojiFrom1930(rawValue) + case 1931: self = EmojiWithSkinTones.emojiFrom1931(rawValue) + case 1932: self = EmojiWithSkinTones.emojiFrom1932(rawValue) + case 1933: self = EmojiWithSkinTones.emojiFrom1933(rawValue) + case 1934: self = EmojiWithSkinTones.emojiFrom1934(rawValue) + case 1935: self = EmojiWithSkinTones.emojiFrom1935(rawValue) + case 1937: self = EmojiWithSkinTones.emojiFrom1937(rawValue) + case 2109: self = EmojiWithSkinTones.emojiFrom2109(rawValue) + case 2111: self = EmojiWithSkinTones.emojiFrom2111(rawValue) + case 2112: self = EmojiWithSkinTones.emojiFrom2112(rawValue) + case 2113: self = EmojiWithSkinTones.emojiFrom2113(rawValue) + case 2116: self = EmojiWithSkinTones.emojiFrom2116(rawValue) + case 2117: self = EmojiWithSkinTones.emojiFrom2117(rawValue) + case 2123: self = EmojiWithSkinTones.emojiFrom2123(rawValue) + case 2125: self = EmojiWithSkinTones.emojiFrom2125(rawValue) + case 2126: self = EmojiWithSkinTones.emojiFrom2126(rawValue) + case 2127: self = EmojiWithSkinTones.emojiFrom2127(rawValue) + case 2129: self = EmojiWithSkinTones.emojiFrom2129(rawValue) + case 2210: self = EmojiWithSkinTones.emojiFrom2210(rawValue) + case 2549: self = EmojiWithSkinTones.emojiFrom2549(rawValue) + case 2558: self = EmojiWithSkinTones.emojiFrom2558(rawValue) + case 2559: self = EmojiWithSkinTones.emojiFrom2559(rawValue) + case 2560: self = EmojiWithSkinTones.emojiFrom2560(rawValue) + case 2561: self = EmojiWithSkinTones.emojiFrom2561(rawValue) + case 2563: self = EmojiWithSkinTones.emojiFrom2563(rawValue) + case 2564: self = EmojiWithSkinTones.emojiFrom2564(rawValue) + case 2565: self = EmojiWithSkinTones.emojiFrom2565(rawValue) + case 2566: self = EmojiWithSkinTones.emojiFrom2566(rawValue) + case 2567: self = EmojiWithSkinTones.emojiFrom2567(rawValue) + case 2572: self = EmojiWithSkinTones.emojiFrom2572(rawValue) + case 2573: self = EmojiWithSkinTones.emojiFrom2573(rawValue) + case 2574: self = EmojiWithSkinTones.emojiFrom2574(rawValue) + case 2575: self = EmojiWithSkinTones.emojiFrom2575(rawValue) + case 2577: self = EmojiWithSkinTones.emojiFrom2577(rawValue) + case 2641: self = EmojiWithSkinTones.emojiFrom2641(rawValue) + case 2642: self = EmojiWithSkinTones.emojiFrom2642(rawValue) + case 2644: self = EmojiWithSkinTones.emojiFrom2644(rawValue) + case 2646: self = EmojiWithSkinTones.emojiFrom2646(rawValue) + case 2649: self = EmojiWithSkinTones.emojiFrom2649(rawValue) + case 2655: self = EmojiWithSkinTones.emojiFrom2655(rawValue) + case 2656: self = EmojiWithSkinTones.emojiFrom2656(rawValue) + case 2657: self = EmojiWithSkinTones.emojiFrom2657(rawValue) + case 2658: self = EmojiWithSkinTones.emojiFrom2658(rawValue) + case 2659: self = EmojiWithSkinTones.emojiFrom2659(rawValue) + case 2663: self = EmojiWithSkinTones.emojiFrom2663(rawValue) + case 2671: self = EmojiWithSkinTones.emojiFrom2671(rawValue) + case 2760: self = EmojiWithSkinTones.emojiFrom2760(rawValue) + case 2761: self = EmojiWithSkinTones.emojiFrom2761(rawValue) + case 2764: self = EmojiWithSkinTones.emojiFrom2764(rawValue) + case 3289: self = EmojiWithSkinTones.emojiFrom3289(rawValue) + case 3295: self = EmojiWithSkinTones.emojiFrom3295(rawValue) + case 3389: self = EmojiWithSkinTones.emojiFrom3389(rawValue) + case 3391: self = EmojiWithSkinTones.emojiFrom3391(rawValue) + case 3392: self = EmojiWithSkinTones.emojiFrom3392(rawValue) + case 3393: self = EmojiWithSkinTones.emojiFrom3393(rawValue) + case 3394: self = EmojiWithSkinTones.emojiFrom3394(rawValue) + case 3396: self = EmojiWithSkinTones.emojiFrom3396(rawValue) + case 3397: self = EmojiWithSkinTones.emojiFrom3397(rawValue) + case 3403: self = EmojiWithSkinTones.emojiFrom3403(rawValue) + case 3404: self = EmojiWithSkinTones.emojiFrom3404(rawValue) + case 3405: self = EmojiWithSkinTones.emojiFrom3405(rawValue) + case 3406: self = EmojiWithSkinTones.emojiFrom3406(rawValue) + case 3407: self = EmojiWithSkinTones.emojiFrom3407(rawValue) + case 3477: self = EmojiWithSkinTones.emojiFrom3477(rawValue) + case 3921: self = EmojiWithSkinTones.emojiFrom3921(rawValue) + case 3922: self = EmojiWithSkinTones.emojiFrom3922(rawValue) + case 3924: self = EmojiWithSkinTones.emojiFrom3924(rawValue) + case 3925: self = EmojiWithSkinTones.emojiFrom3925(rawValue) + case 3926: self = EmojiWithSkinTones.emojiFrom3926(rawValue) + case 3929: self = EmojiWithSkinTones.emojiFrom3929(rawValue) + case 3934: self = EmojiWithSkinTones.emojiFrom3934(rawValue) + case 3935: self = EmojiWithSkinTones.emojiFrom3935(rawValue) + case 3936: self = EmojiWithSkinTones.emojiFrom3936(rawValue) + case 3937: self = EmojiWithSkinTones.emojiFrom3937(rawValue) + case 3938: self = EmojiWithSkinTones.emojiFrom3938(rawValue) + case 3939: self = EmojiWithSkinTones.emojiFrom3939(rawValue) + case 3943: self = EmojiWithSkinTones.emojiFrom3943(rawValue) + case 3948: self = EmojiWithSkinTones.emojiFrom3948(rawValue) + case 3951: self = EmojiWithSkinTones.emojiFrom3951(rawValue) + case 4007: self = EmojiWithSkinTones.emojiFrom4007(rawValue) + case 4046: self = EmojiWithSkinTones.emojiFrom4046(rawValue) + case 4840: self = EmojiWithSkinTones.emojiFrom4840(rawValue) + case 5237: self = EmojiWithSkinTones.emojiFrom5237(rawValue) + case 5370: self = EmojiWithSkinTones.emojiFrom5370(rawValue) + case 6037: self = EmojiWithSkinTones.emojiFrom6037(rawValue) + case 6065: self = EmojiWithSkinTones.emojiFrom6065(rawValue) + case 6579: self = EmojiWithSkinTones.emojiFrom6579(rawValue) + case 6606: self = EmojiWithSkinTones.emojiFrom6606(rawValue) + case 7400: self = EmojiWithSkinTones.emojiFrom7400(rawValue) + case 7428: self = EmojiWithSkinTones.emojiFrom7428(rawValue) + case 56336: self = EmojiWithSkinTones.emojiFrom56336(rawValue) + default: self = EmojiWithSkinTones(unsupportedValue: rawValue) } } + + private static func emojiFrom89(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "⌛": EmojiWithSkinTones(baseEmoji: .hourglass, skinTones: nil), + "⌚": EmojiWithSkinTones(baseEmoji: .watch, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom91(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "⏩": EmojiWithSkinTones(baseEmoji: .fastForward, skinTones: nil), + "⏪": EmojiWithSkinTones(baseEmoji: .rewind, skinTones: nil), + "⏫": EmojiWithSkinTones(baseEmoji: .arrowDoubleUp, skinTones: nil), + "⏬": EmojiWithSkinTones(baseEmoji: .arrowDoubleDown, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom92(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "⏳": EmojiWithSkinTones(baseEmoji: .hourglassFlowingSand, skinTones: nil), + "⏰": EmojiWithSkinTones(baseEmoji: .alarmClock, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom97(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "☕": EmojiWithSkinTones(baseEmoji: .coffee, skinTones: nil), + "☔": EmojiWithSkinTones(baseEmoji: .umbrellaWithRainDrops, skinTones: nil), + "◾": EmojiWithSkinTones(baseEmoji: .blackMediumSmallSquare, skinTones: nil), + "◽": EmojiWithSkinTones(baseEmoji: .whiteMediumSmallSquare, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom98(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "⚓": EmojiWithSkinTones(baseEmoji: .anchor, skinTones: nil), + "⚡": EmojiWithSkinTones(baseEmoji: .zap, skinTones: nil), + "♿": EmojiWithSkinTones(baseEmoji: .wheelchair, skinTones: nil), + "♈": EmojiWithSkinTones(baseEmoji: .aries, skinTones: nil), + "♉": EmojiWithSkinTones(baseEmoji: .taurus, skinTones: nil), + "♊": EmojiWithSkinTones(baseEmoji: .gemini, skinTones: nil), + "♋": EmojiWithSkinTones(baseEmoji: .cancer, skinTones: nil), + "♌": EmojiWithSkinTones(baseEmoji: .leo, skinTones: nil), + "♍": EmojiWithSkinTones(baseEmoji: .virgo, skinTones: nil), + "♎": EmojiWithSkinTones(baseEmoji: .libra, skinTones: nil), + "♏": EmojiWithSkinTones(baseEmoji: .scorpius, skinTones: nil), + "♐": EmojiWithSkinTones(baseEmoji: .sagittarius, skinTones: nil), + "♑": EmojiWithSkinTones(baseEmoji: .capricorn, skinTones: nil), + "♒": EmojiWithSkinTones(baseEmoji: .aquarius, skinTones: nil), + "♓": EmojiWithSkinTones(baseEmoji: .pisces, skinTones: nil), + "⚫": EmojiWithSkinTones(baseEmoji: .blackCircle, skinTones: nil), + "⚪": EmojiWithSkinTones(baseEmoji: .whiteCircle, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom99(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "✋": EmojiWithSkinTones(baseEmoji: .hand, skinTones: nil), + "✊": EmojiWithSkinTones(baseEmoji: .fist, skinTones: nil), + "⛪": EmojiWithSkinTones(baseEmoji: .church, skinTones: nil), + "⛲": EmojiWithSkinTones(baseEmoji: .fountain, skinTones: nil), + "⛺": EmojiWithSkinTones(baseEmoji: .tent, skinTones: nil), + "⛽": EmojiWithSkinTones(baseEmoji: .fuelpump, skinTones: nil), + "⛵": EmojiWithSkinTones(baseEmoji: .boat, skinTones: nil), + "⛅": EmojiWithSkinTones(baseEmoji: .partlySunny, skinTones: nil), + "⛄": EmojiWithSkinTones(baseEmoji: .snowmanWithoutSnow, skinTones: nil), + "⚽": EmojiWithSkinTones(baseEmoji: .soccer, skinTones: nil), + "⚾": EmojiWithSkinTones(baseEmoji: .baseball, skinTones: nil), + "⛳": EmojiWithSkinTones(baseEmoji: .golf, skinTones: nil), + "⛔": EmojiWithSkinTones(baseEmoji: .noEntry, skinTones: nil), + "⛎": EmojiWithSkinTones(baseEmoji: .ophiuchus, skinTones: nil), + "✅": EmojiWithSkinTones(baseEmoji: .whiteCheckMark, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom100(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "✨": EmojiWithSkinTones(baseEmoji: .sparkles, skinTones: nil), + "❓": EmojiWithSkinTones(baseEmoji: .question, skinTones: nil), + "❔": EmojiWithSkinTones(baseEmoji: .greyQuestion, skinTones: nil), + "❕": EmojiWithSkinTones(baseEmoji: .greyExclamation, skinTones: nil), + "❗": EmojiWithSkinTones(baseEmoji: .exclamation, skinTones: nil), + "❌": EmojiWithSkinTones(baseEmoji: .x, skinTones: nil), + "❎": EmojiWithSkinTones(baseEmoji: .negativeSquaredCrossMark, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom101(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "➕": EmojiWithSkinTones(baseEmoji: .heavyPlusSign, skinTones: nil), + "➖": EmojiWithSkinTones(baseEmoji: .heavyMinusSign, skinTones: nil), + "➗": EmojiWithSkinTones(baseEmoji: .heavyDivisionSign, skinTones: nil), + "➰": EmojiWithSkinTones(baseEmoji: .curlyLoop, skinTones: nil), + "➿": EmojiWithSkinTones(baseEmoji: .loop, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom110(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "⭐": EmojiWithSkinTones(baseEmoji: .star, skinTones: nil), + "⭕": EmojiWithSkinTones(baseEmoji: .o, skinTones: nil), + "⬛": EmojiWithSkinTones(baseEmoji: .blackLargeSquare, skinTones: nil), + "⬜": EmojiWithSkinTones(baseEmoji: .whiteLargeSquare, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom652(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "©️": EmojiWithSkinTones(baseEmoji: .copyright, skinTones: nil), + "®️": EmojiWithSkinTones(baseEmoji: .registered, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom732(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "‼️": EmojiWithSkinTones(baseEmoji: .bangbang, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom733(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "⁉️": EmojiWithSkinTones(baseEmoji: .interrobang, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom734(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "#️⃣": EmojiWithSkinTones(baseEmoji: .hash, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom735(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "™️": EmojiWithSkinTones(baseEmoji: .tm, skinTones: nil), + "*️⃣": EmojiWithSkinTones(baseEmoji: .keycapStar, skinTones: nil), + "0️⃣": EmojiWithSkinTones(baseEmoji: .zero, skinTones: nil), + "1️⃣": EmojiWithSkinTones(baseEmoji: .one, skinTones: nil), + "2️⃣": EmojiWithSkinTones(baseEmoji: .two, skinTones: nil), + "3️⃣": EmojiWithSkinTones(baseEmoji: .three, skinTones: nil), + "4️⃣": EmojiWithSkinTones(baseEmoji: .four, skinTones: nil), + "5️⃣": EmojiWithSkinTones(baseEmoji: .five, skinTones: nil), + "6️⃣": EmojiWithSkinTones(baseEmoji: .six, skinTones: nil), + "7️⃣": EmojiWithSkinTones(baseEmoji: .seven, skinTones: nil), + "8️⃣": EmojiWithSkinTones(baseEmoji: .eight, skinTones: nil), + "9️⃣": EmojiWithSkinTones(baseEmoji: .nine, skinTones: nil), + "ℹ️": EmojiWithSkinTones(baseEmoji: .informationSource, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom736(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "↗️": EmojiWithSkinTones(baseEmoji: .arrowUpperRight, skinTones: nil), + "↘️": EmojiWithSkinTones(baseEmoji: .arrowLowerRight, skinTones: nil), + "↙️": EmojiWithSkinTones(baseEmoji: .arrowLowerLeft, skinTones: nil), + "↖️": EmojiWithSkinTones(baseEmoji: .arrowUpperLeft, skinTones: nil), + "↕️": EmojiWithSkinTones(baseEmoji: .arrowUpDown, skinTones: nil), + "↔️": EmojiWithSkinTones(baseEmoji: .leftRightArrow, skinTones: nil), + "↩️": EmojiWithSkinTones(baseEmoji: .leftwardsArrowWithHook, skinTones: nil), + "↪️": EmojiWithSkinTones(baseEmoji: .arrowRightHook, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom740(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "⌨️": EmojiWithSkinTones(baseEmoji: .keyboard, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom742(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "⏱️": EmojiWithSkinTones(baseEmoji: .stopwatch, skinTones: nil), + "⏲️": EmojiWithSkinTones(baseEmoji: .timerClock, skinTones: nil), + "⏭️": EmojiWithSkinTones(baseEmoji: .blackRightPointingDoubleTriangleWithVerticalBar, skinTones: nil), + "⏯️": EmojiWithSkinTones(baseEmoji: .blackRightPointingTriangleWithDoubleVerticalBar, skinTones: nil), + "⏮️": EmojiWithSkinTones(baseEmoji: .blackLeftPointingDoubleTriangleWithVerticalBar, skinTones: nil), + "⏸️": EmojiWithSkinTones(baseEmoji: .doubleVerticalBar, skinTones: nil), + "⏹️": EmojiWithSkinTones(baseEmoji: .blackSquareForStop, skinTones: nil), + "⏺️": EmojiWithSkinTones(baseEmoji: .blackCircleForRecord, skinTones: nil), + "⏏️": EmojiWithSkinTones(baseEmoji: .eject, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom744(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "Ⓜ️": EmojiWithSkinTones(baseEmoji: .m, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom746(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "▶️": EmojiWithSkinTones(baseEmoji: .arrowForward, skinTones: nil), + "▪️": EmojiWithSkinTones(baseEmoji: .blackSmallSquare, skinTones: nil), + "▫️": EmojiWithSkinTones(baseEmoji: .whiteSmallSquare, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom747(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "☠️": EmojiWithSkinTones(baseEmoji: .skullAndCrossbones, skinTones: nil), + "☝️": EmojiWithSkinTones(baseEmoji: .pointUp, skinTones: nil), + "☘️": EmojiWithSkinTones(baseEmoji: .shamrock, skinTones: nil), + "☀️": EmojiWithSkinTones(baseEmoji: .sunny, skinTones: nil), + "☁️": EmojiWithSkinTones(baseEmoji: .cloud, skinTones: nil), + "☂️": EmojiWithSkinTones(baseEmoji: .umbrella, skinTones: nil), + "☃️": EmojiWithSkinTones(baseEmoji: .snowman, skinTones: nil), + "☄️": EmojiWithSkinTones(baseEmoji: .comet, skinTones: nil), + "☎️": EmojiWithSkinTones(baseEmoji: .phone, skinTones: nil), + "◀️": EmojiWithSkinTones(baseEmoji: .arrowBackward, skinTones: nil), + "☑️": EmojiWithSkinTones(baseEmoji: .ballotBoxWithCheck, skinTones: nil), + "◼️": EmojiWithSkinTones(baseEmoji: .blackMediumSquare, skinTones: nil), + "◻️": EmojiWithSkinTones(baseEmoji: .whiteMediumSquare, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom748(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "☺️": EmojiWithSkinTones(baseEmoji: .relaxed, skinTones: nil), + "☹️": EmojiWithSkinTones(baseEmoji: .whiteFrowningFace, skinTones: nil), + "♨️": EmojiWithSkinTones(baseEmoji: .hotsprings, skinTones: nil), + "♠️": EmojiWithSkinTones(baseEmoji: .spades, skinTones: nil), + "♥️": EmojiWithSkinTones(baseEmoji: .hearts, skinTones: nil), + "♦️": EmojiWithSkinTones(baseEmoji: .diamonds, skinTones: nil), + "♣️": EmojiWithSkinTones(baseEmoji: .clubs, skinTones: nil), + "♟️": EmojiWithSkinTones(baseEmoji: .chessPawn, skinTones: nil), + "☢️": EmojiWithSkinTones(baseEmoji: .radioactiveSign, skinTones: nil), + "☣️": EmojiWithSkinTones(baseEmoji: .biohazardSign, skinTones: nil), + "☸️": EmojiWithSkinTones(baseEmoji: .wheelOfDharma, skinTones: nil), + "☯️": EmojiWithSkinTones(baseEmoji: .yinYang, skinTones: nil), + "☦️": EmojiWithSkinTones(baseEmoji: .orthodoxCross, skinTones: nil), + "☪️": EmojiWithSkinTones(baseEmoji: .starAndCrescent, skinTones: nil), + "☮️": EmojiWithSkinTones(baseEmoji: .peaceSymbol, skinTones: nil), + "♀️": EmojiWithSkinTones(baseEmoji: .femaleSign, skinTones: nil), + "♂️": EmojiWithSkinTones(baseEmoji: .maleSign, skinTones: nil), + "♾️": EmojiWithSkinTones(baseEmoji: .infinity, skinTones: nil), + "♻️": EmojiWithSkinTones(baseEmoji: .recycle, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom749(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "⛈️": EmojiWithSkinTones(baseEmoji: .thunderCloudAndRain, skinTones: nil), + "⛑️": EmojiWithSkinTones(baseEmoji: .helmetWithWhiteCross, skinTones: nil), + "⛏️": EmojiWithSkinTones(baseEmoji: .pick, skinTones: nil), + "⚒️": EmojiWithSkinTones(baseEmoji: .hammerAndPick, skinTones: nil), + "⚔️": EmojiWithSkinTones(baseEmoji: .crossedSwords, skinTones: nil), + "⚙️": EmojiWithSkinTones(baseEmoji: .gear, skinTones: nil), + "⚖️": EmojiWithSkinTones(baseEmoji: .scales, skinTones: nil), + "⛓️": EmojiWithSkinTones(baseEmoji: .chains, skinTones: nil), + "⚗️": EmojiWithSkinTones(baseEmoji: .alembic, skinTones: nil), + "⚰️": EmojiWithSkinTones(baseEmoji: .coffin, skinTones: nil), + "⚱️": EmojiWithSkinTones(baseEmoji: .funeralUrn, skinTones: nil), + "⚠️": EmojiWithSkinTones(baseEmoji: .warning, skinTones: nil), + "⚛️": EmojiWithSkinTones(baseEmoji: .atomSymbol, skinTones: nil), + "⚧️": EmojiWithSkinTones(baseEmoji: .transgenderSymbol, skinTones: nil), + "⚕️": EmojiWithSkinTones(baseEmoji: .medicalSymbol, skinTones: nil), + "⚜️": EmojiWithSkinTones(baseEmoji: .fleurDeLis, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom750(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "✌️": EmojiWithSkinTones(baseEmoji: .v, skinTones: nil), + "✍️": EmojiWithSkinTones(baseEmoji: .writingHand, skinTones: nil), + "⛷️": EmojiWithSkinTones(baseEmoji: .skier, skinTones: nil), + "⛹️": EmojiWithSkinTones(baseEmoji: .personWithBall, skinTones: nil), + "⛰️": EmojiWithSkinTones(baseEmoji: .mountain, skinTones: nil), + "⛩️": EmojiWithSkinTones(baseEmoji: .shintoShrine, skinTones: nil), + "⛴️": EmojiWithSkinTones(baseEmoji: .ferry, skinTones: nil), + "✈️": EmojiWithSkinTones(baseEmoji: .airplane, skinTones: nil), + "⛱️": EmojiWithSkinTones(baseEmoji: .umbrellaOnGround, skinTones: nil), + "❄️": EmojiWithSkinTones(baseEmoji: .snowflake, skinTones: nil), + "⛸️": EmojiWithSkinTones(baseEmoji: .iceSkate, skinTones: nil), + "✉️": EmojiWithSkinTones(baseEmoji: .email, skinTones: nil), + "✏️": EmojiWithSkinTones(baseEmoji: .pencil2, skinTones: nil), + "✒️": EmojiWithSkinTones(baseEmoji: .blackNib, skinTones: nil), + "✂️": EmojiWithSkinTones(baseEmoji: .scissors, skinTones: nil), + "✡️": EmojiWithSkinTones(baseEmoji: .starOfDavid, skinTones: nil), + "✝️": EmojiWithSkinTones(baseEmoji: .latinCross, skinTones: nil), + "✖️": EmojiWithSkinTones(baseEmoji: .heavyMultiplicationX, skinTones: nil), + "✔️": EmojiWithSkinTones(baseEmoji: .heavyCheckMark, skinTones: nil), + "✳️": EmojiWithSkinTones(baseEmoji: .eightSpokedAsterisk, skinTones: nil), + "✴️": EmojiWithSkinTones(baseEmoji: .eightPointedBlackStar, skinTones: nil), + "❇️": EmojiWithSkinTones(baseEmoji: .sparkle, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom751(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "❣️": EmojiWithSkinTones(baseEmoji: .heavyHeartExclamationMarkOrnament, skinTones: nil), + "❤️": EmojiWithSkinTones(baseEmoji: .heart, skinTones: nil), + "➡️": EmojiWithSkinTones(baseEmoji: .arrowRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom755(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "⤴️": EmojiWithSkinTones(baseEmoji: .arrowHeadingUp, skinTones: nil), + "⤵️": EmojiWithSkinTones(baseEmoji: .arrowHeadingDown, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom760(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "⬆️": EmojiWithSkinTones(baseEmoji: .arrowUp, skinTones: nil), + "⬇️": EmojiWithSkinTones(baseEmoji: .arrowDown, skinTones: nil), + "⬅️": EmojiWithSkinTones(baseEmoji: .arrowLeft, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom773(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "〰️": EmojiWithSkinTones(baseEmoji: .wavyDash, skinTones: nil), + "〽️": EmojiWithSkinTones(baseEmoji: .partAlternationMark, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom779(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "㊗️": EmojiWithSkinTones(baseEmoji: .congratulations, skinTones: nil), + "㊙️": EmojiWithSkinTones(baseEmoji: .secret, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1269(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🀄": EmojiWithSkinTones(baseEmoji: .mahjong, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1271(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🃏": EmojiWithSkinTones(baseEmoji: .blackJoker, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1273(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🆎": EmojiWithSkinTones(baseEmoji: .ab, skinTones: nil), + "🆑": EmojiWithSkinTones(baseEmoji: .cl, skinTones: nil), + "🆒": EmojiWithSkinTones(baseEmoji: .cool, skinTones: nil), + "🆓": EmojiWithSkinTones(baseEmoji: .free, skinTones: nil), + "🆔": EmojiWithSkinTones(baseEmoji: .id, skinTones: nil), + "🆕": EmojiWithSkinTones(baseEmoji: .new, skinTones: nil), + "🆖": EmojiWithSkinTones(baseEmoji: .ng, skinTones: nil), + "🆗": EmojiWithSkinTones(baseEmoji: .ok, skinTones: nil), + "🆘": EmojiWithSkinTones(baseEmoji: .sos, skinTones: nil), + "🆙": EmojiWithSkinTones(baseEmoji: .up, skinTones: nil), + "🆚": EmojiWithSkinTones(baseEmoji: .vs, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1274(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🈁": EmojiWithSkinTones(baseEmoji: .koko, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1275(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🈶": EmojiWithSkinTones(baseEmoji: .u6709, skinTones: nil), + "🈯": EmojiWithSkinTones(baseEmoji: .u6307, skinTones: nil), + "🉐": EmojiWithSkinTones(baseEmoji: .ideographAdvantage, skinTones: nil), + "🈹": EmojiWithSkinTones(baseEmoji: .u5272, skinTones: nil), + "🈚": EmojiWithSkinTones(baseEmoji: .u7121, skinTones: nil), + "🈲": EmojiWithSkinTones(baseEmoji: .u7981, skinTones: nil), + "🉑": EmojiWithSkinTones(baseEmoji: .accept, skinTones: nil), + "🈸": EmojiWithSkinTones(baseEmoji: .u7533, skinTones: nil), + "🈴": EmojiWithSkinTones(baseEmoji: .u5408, skinTones: nil), + "🈳": EmojiWithSkinTones(baseEmoji: .u7a7a, skinTones: nil), + "🈺": EmojiWithSkinTones(baseEmoji: .u55b6, skinTones: nil), + "🈵": EmojiWithSkinTones(baseEmoji: .u6e80, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1277(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🌷": EmojiWithSkinTones(baseEmoji: .tulip, skinTones: nil), + "🌱": EmojiWithSkinTones(baseEmoji: .seedling, skinTones: nil), + "🌲": EmojiWithSkinTones(baseEmoji: .evergreenTree, skinTones: nil), + "🌳": EmojiWithSkinTones(baseEmoji: .deciduousTree, skinTones: nil), + "🌴": EmojiWithSkinTones(baseEmoji: .palmTree, skinTones: nil), + "🌵": EmojiWithSkinTones(baseEmoji: .cactus, skinTones: nil), + "🌰": EmojiWithSkinTones(baseEmoji: .chestnut, skinTones: nil), + "🌭": EmojiWithSkinTones(baseEmoji: .hotdog, skinTones: nil), + "🌮": EmojiWithSkinTones(baseEmoji: .taco, skinTones: nil), + "🌯": EmojiWithSkinTones(baseEmoji: .burrito, skinTones: nil), + "🌍": EmojiWithSkinTones(baseEmoji: .earthAfrica, skinTones: nil), + "🌎": EmojiWithSkinTones(baseEmoji: .earthAmericas, skinTones: nil), + "🌏": EmojiWithSkinTones(baseEmoji: .earthAsia, skinTones: nil), + "🌐": EmojiWithSkinTones(baseEmoji: .globeWithMeridians, skinTones: nil), + "🌋": EmojiWithSkinTones(baseEmoji: .volcano, skinTones: nil), + "🌁": EmojiWithSkinTones(baseEmoji: .foggy, skinTones: nil), + "🌃": EmojiWithSkinTones(baseEmoji: .nightWithStars, skinTones: nil), + "🌄": EmojiWithSkinTones(baseEmoji: .sunriseOverMountains, skinTones: nil), + "🌅": EmojiWithSkinTones(baseEmoji: .sunrise, skinTones: nil), + "🌆": EmojiWithSkinTones(baseEmoji: .citySunset, skinTones: nil), + "🌇": EmojiWithSkinTones(baseEmoji: .citySunrise, skinTones: nil), + "🌉": EmojiWithSkinTones(baseEmoji: .bridgeAtNight, skinTones: nil), + "🌑": EmojiWithSkinTones(baseEmoji: .newMoon, skinTones: nil), + "🌒": EmojiWithSkinTones(baseEmoji: .waxingCrescentMoon, skinTones: nil), + "🌓": EmojiWithSkinTones(baseEmoji: .firstQuarterMoon, skinTones: nil), + "🌔": EmojiWithSkinTones(baseEmoji: .moon, skinTones: nil), + "🌕": EmojiWithSkinTones(baseEmoji: .fullMoon, skinTones: nil), + "🌖": EmojiWithSkinTones(baseEmoji: .waningGibbousMoon, skinTones: nil), + "🌗": EmojiWithSkinTones(baseEmoji: .lastQuarterMoon, skinTones: nil), + "🌘": EmojiWithSkinTones(baseEmoji: .waningCrescentMoon, skinTones: nil), + "🌙": EmojiWithSkinTones(baseEmoji: .crescentMoon, skinTones: nil), + "🌚": EmojiWithSkinTones(baseEmoji: .newMoonWithFace, skinTones: nil), + "🌛": EmojiWithSkinTones(baseEmoji: .firstQuarterMoonWithFace, skinTones: nil), + "🌜": EmojiWithSkinTones(baseEmoji: .lastQuarterMoonWithFace, skinTones: nil), + "🌝": EmojiWithSkinTones(baseEmoji: .fullMoonWithFace, skinTones: nil), + "🌞": EmojiWithSkinTones(baseEmoji: .sunWithFace, skinTones: nil), + "🌟": EmojiWithSkinTones(baseEmoji: .star2, skinTones: nil), + "🌠": EmojiWithSkinTones(baseEmoji: .stars, skinTones: nil), + "🌌": EmojiWithSkinTones(baseEmoji: .milkyWay, skinTones: nil), + "🌀": EmojiWithSkinTones(baseEmoji: .cyclone, skinTones: nil), + "🌈": EmojiWithSkinTones(baseEmoji: .rainbow, skinTones: nil), + "🌂": EmojiWithSkinTones(baseEmoji: .closedUmbrella, skinTones: nil), + "🌊": EmojiWithSkinTones(baseEmoji: .ocean, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1278(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🎅": EmojiWithSkinTones(baseEmoji: .santa, skinTones: nil), + "🌸": EmojiWithSkinTones(baseEmoji: .cherryBlossom, skinTones: nil), + "🌹": EmojiWithSkinTones(baseEmoji: .rose, skinTones: nil), + "🌺": EmojiWithSkinTones(baseEmoji: .hibiscus, skinTones: nil), + "🌻": EmojiWithSkinTones(baseEmoji: .sunflower, skinTones: nil), + "🌼": EmojiWithSkinTones(baseEmoji: .blossom, skinTones: nil), + "🌾": EmojiWithSkinTones(baseEmoji: .earOfRice, skinTones: nil), + "🌿": EmojiWithSkinTones(baseEmoji: .herb, skinTones: nil), + "🍀": EmojiWithSkinTones(baseEmoji: .fourLeafClover, skinTones: nil), + "🍁": EmojiWithSkinTones(baseEmoji: .mapleLeaf, skinTones: nil), + "🍂": EmojiWithSkinTones(baseEmoji: .fallenLeaf, skinTones: nil), + "🍃": EmojiWithSkinTones(baseEmoji: .leaves, skinTones: nil), + "🍄": EmojiWithSkinTones(baseEmoji: .mushroom, skinTones: nil), + "🍇": EmojiWithSkinTones(baseEmoji: .grapes, skinTones: nil), + "🍈": EmojiWithSkinTones(baseEmoji: .melon, skinTones: nil), + "🍉": EmojiWithSkinTones(baseEmoji: .watermelon, skinTones: nil), + "🍊": EmojiWithSkinTones(baseEmoji: .tangerine, skinTones: nil), + "🍋": EmojiWithSkinTones(baseEmoji: .lemon, skinTones: nil), + "🍌": EmojiWithSkinTones(baseEmoji: .banana, skinTones: nil), + "🍍": EmojiWithSkinTones(baseEmoji: .pineapple, skinTones: nil), + "🍎": EmojiWithSkinTones(baseEmoji: .apple, skinTones: nil), + "🍏": EmojiWithSkinTones(baseEmoji: .greenApple, skinTones: nil), + "🍐": EmojiWithSkinTones(baseEmoji: .pear, skinTones: nil), + "🍑": EmojiWithSkinTones(baseEmoji: .peach, skinTones: nil), + "🍒": EmojiWithSkinTones(baseEmoji: .cherries, skinTones: nil), + "🍓": EmojiWithSkinTones(baseEmoji: .strawberry, skinTones: nil), + "🍅": EmojiWithSkinTones(baseEmoji: .tomato, skinTones: nil), + "🍆": EmojiWithSkinTones(baseEmoji: .eggplant, skinTones: nil), + "🌽": EmojiWithSkinTones(baseEmoji: .corn, skinTones: nil), + "🍞": EmojiWithSkinTones(baseEmoji: .bread, skinTones: nil), + "🍖": EmojiWithSkinTones(baseEmoji: .meatOnBone, skinTones: nil), + "🍗": EmojiWithSkinTones(baseEmoji: .poultryLeg, skinTones: nil), + "🍔": EmojiWithSkinTones(baseEmoji: .hamburger, skinTones: nil), + "🍟": EmojiWithSkinTones(baseEmoji: .fries, skinTones: nil), + "🍕": EmojiWithSkinTones(baseEmoji: .pizza, skinTones: nil), + "🍳": EmojiWithSkinTones(baseEmoji: .friedEgg, skinTones: nil), + "🍲": EmojiWithSkinTones(baseEmoji: .stew, skinTones: nil), + "🍿": EmojiWithSkinTones(baseEmoji: .popcorn, skinTones: nil), + "🍱": EmojiWithSkinTones(baseEmoji: .bento, skinTones: nil), + "🍘": EmojiWithSkinTones(baseEmoji: .riceCracker, skinTones: nil), + "🍙": EmojiWithSkinTones(baseEmoji: .riceBall, skinTones: nil), + "🍚": EmojiWithSkinTones(baseEmoji: .rice, skinTones: nil), + "🍛": EmojiWithSkinTones(baseEmoji: .curry, skinTones: nil), + "🍜": EmojiWithSkinTones(baseEmoji: .ramen, skinTones: nil), + "🍝": EmojiWithSkinTones(baseEmoji: .spaghetti, skinTones: nil), + "🍠": EmojiWithSkinTones(baseEmoji: .sweetPotato, skinTones: nil), + "🍢": EmojiWithSkinTones(baseEmoji: .oden, skinTones: nil), + "🍣": EmojiWithSkinTones(baseEmoji: .sushi, skinTones: nil), + "🍤": EmojiWithSkinTones(baseEmoji: .friedShrimp, skinTones: nil), + "🍥": EmojiWithSkinTones(baseEmoji: .fishCake, skinTones: nil), + "🍡": EmojiWithSkinTones(baseEmoji: .dango, skinTones: nil), + "🍦": EmojiWithSkinTones(baseEmoji: .icecream, skinTones: nil), + "🍧": EmojiWithSkinTones(baseEmoji: .shavedIce, skinTones: nil), + "🍨": EmojiWithSkinTones(baseEmoji: .iceCream, skinTones: nil), + "🍩": EmojiWithSkinTones(baseEmoji: .doughnut, skinTones: nil), + "🍪": EmojiWithSkinTones(baseEmoji: .cookie, skinTones: nil), + "🎂": EmojiWithSkinTones(baseEmoji: .birthday, skinTones: nil), + "🍰": EmojiWithSkinTones(baseEmoji: .cake, skinTones: nil), + "🍫": EmojiWithSkinTones(baseEmoji: .chocolateBar, skinTones: nil), + "🍬": EmojiWithSkinTones(baseEmoji: .candy, skinTones: nil), + "🍭": EmojiWithSkinTones(baseEmoji: .lollipop, skinTones: nil), + "🍮": EmojiWithSkinTones(baseEmoji: .custard, skinTones: nil), + "🍯": EmojiWithSkinTones(baseEmoji: .honeyPot, skinTones: nil), + "🍼": EmojiWithSkinTones(baseEmoji: .babyBottle, skinTones: nil), + "🍵": EmojiWithSkinTones(baseEmoji: .tea, skinTones: nil), + "🍶": EmojiWithSkinTones(baseEmoji: .sake, skinTones: nil), + "🍾": EmojiWithSkinTones(baseEmoji: .champagne, skinTones: nil), + "🍷": EmojiWithSkinTones(baseEmoji: .wineGlass, skinTones: nil), + "🍸": EmojiWithSkinTones(baseEmoji: .cocktail, skinTones: nil), + "🍹": EmojiWithSkinTones(baseEmoji: .tropicalDrink, skinTones: nil), + "🍺": EmojiWithSkinTones(baseEmoji: .beer, skinTones: nil), + "🍻": EmojiWithSkinTones(baseEmoji: .beers, skinTones: nil), + "🍴": EmojiWithSkinTones(baseEmoji: .forkAndKnife, skinTones: nil), + "🎃": EmojiWithSkinTones(baseEmoji: .jackOLantern, skinTones: nil), + "🎄": EmojiWithSkinTones(baseEmoji: .christmasTree, skinTones: nil), + "🎆": EmojiWithSkinTones(baseEmoji: .fireworks, skinTones: nil), + "🎇": EmojiWithSkinTones(baseEmoji: .sparkler, skinTones: nil), + "🎈": EmojiWithSkinTones(baseEmoji: .balloon, skinTones: nil), + "🎉": EmojiWithSkinTones(baseEmoji: .tada, skinTones: nil), + "🎊": EmojiWithSkinTones(baseEmoji: .confettiBall, skinTones: nil), + "🎋": EmojiWithSkinTones(baseEmoji: .tanabataTree, skinTones: nil), + "🎍": EmojiWithSkinTones(baseEmoji: .bamboo, skinTones: nil), + "🎎": EmojiWithSkinTones(baseEmoji: .dolls, skinTones: nil), + "🎏": EmojiWithSkinTones(baseEmoji: .flags, skinTones: nil), + "🎐": EmojiWithSkinTones(baseEmoji: .windChime, skinTones: nil), + "🎑": EmojiWithSkinTones(baseEmoji: .riceScene, skinTones: nil), + "🎀": EmojiWithSkinTones(baseEmoji: .ribbon, skinTones: nil), + "🎁": EmojiWithSkinTones(baseEmoji: .gift, skinTones: nil), + "🎒": EmojiWithSkinTones(baseEmoji: .schoolSatchel, skinTones: nil), + "🎓": EmojiWithSkinTones(baseEmoji: .mortarBoard, skinTones: nil), + "🎌": EmojiWithSkinTones(baseEmoji: .crossedFlags, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1279(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏃": EmojiWithSkinTones(baseEmoji: .runner, skinTones: nil), + "🏇": EmojiWithSkinTones(baseEmoji: .horseRacing, skinTones: nil), + "🏂": EmojiWithSkinTones(baseEmoji: .snowboarder, skinTones: nil), + "🏄": EmojiWithSkinTones(baseEmoji: .surfer, skinTones: nil), + "🏊": EmojiWithSkinTones(baseEmoji: .swimmer, skinTones: nil), + "🏻": EmojiWithSkinTones(baseEmoji: .skinTone2, skinTones: nil), + "🏼": EmojiWithSkinTones(baseEmoji: .skinTone3, skinTones: nil), + "🏽": EmojiWithSkinTones(baseEmoji: .skinTone4, skinTones: nil), + "🏾": EmojiWithSkinTones(baseEmoji: .skinTone5, skinTones: nil), + "🏿": EmojiWithSkinTones(baseEmoji: .skinTone6, skinTones: nil), + "🏺": EmojiWithSkinTones(baseEmoji: .amphora, skinTones: nil), + "🏠": EmojiWithSkinTones(baseEmoji: .house, skinTones: nil), + "🏡": EmojiWithSkinTones(baseEmoji: .houseWithGarden, skinTones: nil), + "🏢": EmojiWithSkinTones(baseEmoji: .office, skinTones: nil), + "🏣": EmojiWithSkinTones(baseEmoji: .postOffice, skinTones: nil), + "🏤": EmojiWithSkinTones(baseEmoji: .europeanPostOffice, skinTones: nil), + "🏥": EmojiWithSkinTones(baseEmoji: .hospital, skinTones: nil), + "🏦": EmojiWithSkinTones(baseEmoji: .bank, skinTones: nil), + "🏨": EmojiWithSkinTones(baseEmoji: .hotel, skinTones: nil), + "🏩": EmojiWithSkinTones(baseEmoji: .loveHotel, skinTones: nil), + "🏪": EmojiWithSkinTones(baseEmoji: .convenienceStore, skinTones: nil), + "🏫": EmojiWithSkinTones(baseEmoji: .school, skinTones: nil), + "🏬": EmojiWithSkinTones(baseEmoji: .departmentStore, skinTones: nil), + "🏭": EmojiWithSkinTones(baseEmoji: .factory, skinTones: nil), + "🏯": EmojiWithSkinTones(baseEmoji: .japaneseCastle, skinTones: nil), + "🏰": EmojiWithSkinTones(baseEmoji: .europeanCastle, skinTones: nil), + "🎠": EmojiWithSkinTones(baseEmoji: .carouselHorse, skinTones: nil), + "🎡": EmojiWithSkinTones(baseEmoji: .ferrisWheel, skinTones: nil), + "🎢": EmojiWithSkinTones(baseEmoji: .rollerCoaster, skinTones: nil), + "🎪": EmojiWithSkinTones(baseEmoji: .circusTent, skinTones: nil), + "🎫": EmojiWithSkinTones(baseEmoji: .ticket, skinTones: nil), + "🏆": EmojiWithSkinTones(baseEmoji: .trophy, skinTones: nil), + "🏅": EmojiWithSkinTones(baseEmoji: .sportsMedal, skinTones: nil), + "🏀": EmojiWithSkinTones(baseEmoji: .basketball, skinTones: nil), + "🏐": EmojiWithSkinTones(baseEmoji: .volleyball, skinTones: nil), + "🏈": EmojiWithSkinTones(baseEmoji: .football, skinTones: nil), + "🏉": EmojiWithSkinTones(baseEmoji: .rugbyFootball, skinTones: nil), + "🎾": EmojiWithSkinTones(baseEmoji: .tennis, skinTones: nil), + "🎳": EmojiWithSkinTones(baseEmoji: .bowling, skinTones: nil), + "🏏": EmojiWithSkinTones(baseEmoji: .cricketBatAndBall, skinTones: nil), + "🏑": EmojiWithSkinTones(baseEmoji: .fieldHockeyStickAndBall, skinTones: nil), + "🏒": EmojiWithSkinTones(baseEmoji: .iceHockeyStickAndPuck, skinTones: nil), + "🏓": EmojiWithSkinTones(baseEmoji: .tableTennisPaddleAndBall, skinTones: nil), + "🏸": EmojiWithSkinTones(baseEmoji: .badmintonRacquetAndShuttlecock, skinTones: nil), + "🎣": EmojiWithSkinTones(baseEmoji: .fishingPoleAndFish, skinTones: nil), + "🎽": EmojiWithSkinTones(baseEmoji: .runningShirtWithSash, skinTones: nil), + "🎿": EmojiWithSkinTones(baseEmoji: .ski, skinTones: nil), + "🎯": EmojiWithSkinTones(baseEmoji: .dart, skinTones: nil), + "🎱": EmojiWithSkinTones(baseEmoji: .eightBall, skinTones: nil), + "🎮": EmojiWithSkinTones(baseEmoji: .videoGame, skinTones: nil), + "🎰": EmojiWithSkinTones(baseEmoji: .slotMachine, skinTones: nil), + "🎲": EmojiWithSkinTones(baseEmoji: .gameDie, skinTones: nil), + "🎴": EmojiWithSkinTones(baseEmoji: .flowerPlayingCards, skinTones: nil), + "🎭": EmojiWithSkinTones(baseEmoji: .performingArts, skinTones: nil), + "🎨": EmojiWithSkinTones(baseEmoji: .art, skinTones: nil), + "🎩": EmojiWithSkinTones(baseEmoji: .tophat, skinTones: nil), + "🎼": EmojiWithSkinTones(baseEmoji: .musicalScore, skinTones: nil), + "🎵": EmojiWithSkinTones(baseEmoji: .musicalNote, skinTones: nil), + "🎶": EmojiWithSkinTones(baseEmoji: .notes, skinTones: nil), + "🎤": EmojiWithSkinTones(baseEmoji: .microphone, skinTones: nil), + "🎧": EmojiWithSkinTones(baseEmoji: .headphones, skinTones: nil), + "🎷": EmojiWithSkinTones(baseEmoji: .saxophone, skinTones: nil), + "🎸": EmojiWithSkinTones(baseEmoji: .guitar, skinTones: nil), + "🎹": EmojiWithSkinTones(baseEmoji: .musicalKeyboard, skinTones: nil), + "🎺": EmojiWithSkinTones(baseEmoji: .trumpet, skinTones: nil), + "🎻": EmojiWithSkinTones(baseEmoji: .violin, skinTones: nil), + "🎥": EmojiWithSkinTones(baseEmoji: .movieCamera, skinTones: nil), + "🎬": EmojiWithSkinTones(baseEmoji: .clapper, skinTones: nil), + "🏮": EmojiWithSkinTones(baseEmoji: .izakayaLantern, skinTones: nil), + "🏹": EmojiWithSkinTones(baseEmoji: .bowAndArrow, skinTones: nil), + "🏧": EmojiWithSkinTones(baseEmoji: .atm, skinTones: nil), + "🎦": EmojiWithSkinTones(baseEmoji: .cinema, skinTones: nil), + "🏁": EmojiWithSkinTones(baseEmoji: .checkeredFlag, skinTones: nil), + "🏴": EmojiWithSkinTones(baseEmoji: .wavingBlackFlag, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1280(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👋": EmojiWithSkinTones(baseEmoji: .wave, skinTones: nil), + "👌": EmojiWithSkinTones(baseEmoji: .okHand, skinTones: nil), + "👈": EmojiWithSkinTones(baseEmoji: .pointLeft, skinTones: nil), + "👉": EmojiWithSkinTones(baseEmoji: .pointRight, skinTones: nil), + "👆": EmojiWithSkinTones(baseEmoji: .pointUp2, skinTones: nil), + "👇": EmojiWithSkinTones(baseEmoji: .pointDown, skinTones: nil), + "👍": EmojiWithSkinTones(baseEmoji: .plusOne, skinTones: nil), + "👎": EmojiWithSkinTones(baseEmoji: .negativeOne, skinTones: nil), + "👊": EmojiWithSkinTones(baseEmoji: .facepunch, skinTones: nil), + "👏": EmojiWithSkinTones(baseEmoji: .clap, skinTones: nil), + "👐": EmojiWithSkinTones(baseEmoji: .openHands, skinTones: nil), + "👂": EmojiWithSkinTones(baseEmoji: .ear, skinTones: nil), + "👃": EmojiWithSkinTones(baseEmoji: .nose, skinTones: nil), + "👀": EmojiWithSkinTones(baseEmoji: .eyes, skinTones: nil), + "👅": EmojiWithSkinTones(baseEmoji: .tongue, skinTones: nil), + "👄": EmojiWithSkinTones(baseEmoji: .lips, skinTones: nil), + "👣": EmojiWithSkinTones(baseEmoji: .footprints, skinTones: nil), + "🐵": EmojiWithSkinTones(baseEmoji: .monkeyFace, skinTones: nil), + "🐒": EmojiWithSkinTones(baseEmoji: .monkey, skinTones: nil), + "🐶": EmojiWithSkinTones(baseEmoji: .dog, skinTones: nil), + "🐕": EmojiWithSkinTones(baseEmoji: .dog2, skinTones: nil), + "🐩": EmojiWithSkinTones(baseEmoji: .poodle, skinTones: nil), + "🐺": EmojiWithSkinTones(baseEmoji: .wolf, skinTones: nil), + "🐱": EmojiWithSkinTones(baseEmoji: .cat, skinTones: nil), + "🐈": EmojiWithSkinTones(baseEmoji: .cat2, skinTones: nil), + "🐯": EmojiWithSkinTones(baseEmoji: .tiger, skinTones: nil), + "🐅": EmojiWithSkinTones(baseEmoji: .tiger2, skinTones: nil), + "🐆": EmojiWithSkinTones(baseEmoji: .leopard, skinTones: nil), + "🐴": EmojiWithSkinTones(baseEmoji: .horse, skinTones: nil), + "🐎": EmojiWithSkinTones(baseEmoji: .racehorse, skinTones: nil), + "🐮": EmojiWithSkinTones(baseEmoji: .cow, skinTones: nil), + "🐂": EmojiWithSkinTones(baseEmoji: .ox, skinTones: nil), + "🐃": EmojiWithSkinTones(baseEmoji: .waterBuffalo, skinTones: nil), + "🐄": EmojiWithSkinTones(baseEmoji: .cow2, skinTones: nil), + "🐷": EmojiWithSkinTones(baseEmoji: .pig, skinTones: nil), + "🐖": EmojiWithSkinTones(baseEmoji: .pig2, skinTones: nil), + "🐗": EmojiWithSkinTones(baseEmoji: .boar, skinTones: nil), + "🐽": EmojiWithSkinTones(baseEmoji: .pigNose, skinTones: nil), + "🐏": EmojiWithSkinTones(baseEmoji: .ram, skinTones: nil), + "🐑": EmojiWithSkinTones(baseEmoji: .sheep, skinTones: nil), + "🐐": EmojiWithSkinTones(baseEmoji: .goat, skinTones: nil), + "🐪": EmojiWithSkinTones(baseEmoji: .dromedaryCamel, skinTones: nil), + "🐫": EmojiWithSkinTones(baseEmoji: .camel, skinTones: nil), + "🐘": EmojiWithSkinTones(baseEmoji: .elephant, skinTones: nil), + "🐭": EmojiWithSkinTones(baseEmoji: .mouse, skinTones: nil), + "🐁": EmojiWithSkinTones(baseEmoji: .mouse2, skinTones: nil), + "🐀": EmojiWithSkinTones(baseEmoji: .rat, skinTones: nil), + "🐹": EmojiWithSkinTones(baseEmoji: .hamster, skinTones: nil), + "🐰": EmojiWithSkinTones(baseEmoji: .rabbit, skinTones: nil), + "🐇": EmojiWithSkinTones(baseEmoji: .rabbit2, skinTones: nil), + "🐻": EmojiWithSkinTones(baseEmoji: .bear, skinTones: nil), + "🐨": EmojiWithSkinTones(baseEmoji: .koala, skinTones: nil), + "🐼": EmojiWithSkinTones(baseEmoji: .pandaFace, skinTones: nil), + "🐾": EmojiWithSkinTones(baseEmoji: .feet, skinTones: nil), + "🐔": EmojiWithSkinTones(baseEmoji: .chicken, skinTones: nil), + "🐓": EmojiWithSkinTones(baseEmoji: .rooster, skinTones: nil), + "🐣": EmojiWithSkinTones(baseEmoji: .hatchingChick, skinTones: nil), + "🐤": EmojiWithSkinTones(baseEmoji: .babyChick, skinTones: nil), + "🐥": EmojiWithSkinTones(baseEmoji: .hatchedChick, skinTones: nil), + "🐦": EmojiWithSkinTones(baseEmoji: .bird, skinTones: nil), + "🐧": EmojiWithSkinTones(baseEmoji: .penguin, skinTones: nil), + "🐸": EmojiWithSkinTones(baseEmoji: .frog, skinTones: nil), + "🐊": EmojiWithSkinTones(baseEmoji: .crocodile, skinTones: nil), + "🐢": EmojiWithSkinTones(baseEmoji: .turtle, skinTones: nil), + "🐍": EmojiWithSkinTones(baseEmoji: .snake, skinTones: nil), + "🐲": EmojiWithSkinTones(baseEmoji: .dragonFace, skinTones: nil), + "🐉": EmojiWithSkinTones(baseEmoji: .dragon, skinTones: nil), + "🐳": EmojiWithSkinTones(baseEmoji: .whale, skinTones: nil), + "🐋": EmojiWithSkinTones(baseEmoji: .whale2, skinTones: nil), + "🐬": EmojiWithSkinTones(baseEmoji: .dolphin, skinTones: nil), + "🐟": EmojiWithSkinTones(baseEmoji: .fish, skinTones: nil), + "🐠": EmojiWithSkinTones(baseEmoji: .tropicalFish, skinTones: nil), + "🐡": EmojiWithSkinTones(baseEmoji: .blowfish, skinTones: nil), + "🐙": EmojiWithSkinTones(baseEmoji: .octopus, skinTones: nil), + "🐚": EmojiWithSkinTones(baseEmoji: .shell, skinTones: nil), + "🐌": EmojiWithSkinTones(baseEmoji: .snail, skinTones: nil), + "🐛": EmojiWithSkinTones(baseEmoji: .bug, skinTones: nil), + "🐜": EmojiWithSkinTones(baseEmoji: .ant, skinTones: nil), + "🐝": EmojiWithSkinTones(baseEmoji: .bee, skinTones: nil), + "🐞": EmojiWithSkinTones(baseEmoji: .ladybug, skinTones: nil), + "👓": EmojiWithSkinTones(baseEmoji: .eyeglasses, skinTones: nil), + "👔": EmojiWithSkinTones(baseEmoji: .necktie, skinTones: nil), + "👕": EmojiWithSkinTones(baseEmoji: .shirt, skinTones: nil), + "👖": EmojiWithSkinTones(baseEmoji: .jeans, skinTones: nil), + "👗": EmojiWithSkinTones(baseEmoji: .dress, skinTones: nil), + "👘": EmojiWithSkinTones(baseEmoji: .kimono, skinTones: nil), + "👙": EmojiWithSkinTones(baseEmoji: .bikini, skinTones: nil), + "👚": EmojiWithSkinTones(baseEmoji: .womansClothes, skinTones: nil), + "👛": EmojiWithSkinTones(baseEmoji: .purse, skinTones: nil), + "👜": EmojiWithSkinTones(baseEmoji: .handbag, skinTones: nil), + "👝": EmojiWithSkinTones(baseEmoji: .pouch, skinTones: nil), + "👞": EmojiWithSkinTones(baseEmoji: .mansShoe, skinTones: nil), + "👟": EmojiWithSkinTones(baseEmoji: .athleticShoe, skinTones: nil), + "👠": EmojiWithSkinTones(baseEmoji: .highHeel, skinTones: nil), + "👡": EmojiWithSkinTones(baseEmoji: .sandal, skinTones: nil), + "👢": EmojiWithSkinTones(baseEmoji: .boot, skinTones: nil), + "👑": EmojiWithSkinTones(baseEmoji: .crown, skinTones: nil), + "👒": EmojiWithSkinTones(baseEmoji: .womansHat, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1281(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👿": EmojiWithSkinTones(baseEmoji: .imp, skinTones: nil), + "💀": EmojiWithSkinTones(baseEmoji: .skull, skinTones: nil), + "💩": EmojiWithSkinTones(baseEmoji: .hankey, skinTones: nil), + "👹": EmojiWithSkinTones(baseEmoji: .japaneseOgre, skinTones: nil), + "👺": EmojiWithSkinTones(baseEmoji: .japaneseGoblin, skinTones: nil), + "👻": EmojiWithSkinTones(baseEmoji: .ghost, skinTones: nil), + "👽": EmojiWithSkinTones(baseEmoji: .alien, skinTones: nil), + "👾": EmojiWithSkinTones(baseEmoji: .spaceInvader, skinTones: nil), + "💌": EmojiWithSkinTones(baseEmoji: .loveLetter, skinTones: nil), + "💘": EmojiWithSkinTones(baseEmoji: .cupid, skinTones: nil), + "💝": EmojiWithSkinTones(baseEmoji: .giftHeart, skinTones: nil), + "💖": EmojiWithSkinTones(baseEmoji: .sparklingHeart, skinTones: nil), + "💗": EmojiWithSkinTones(baseEmoji: .heartpulse, skinTones: nil), + "💓": EmojiWithSkinTones(baseEmoji: .heartbeat, skinTones: nil), + "💞": EmojiWithSkinTones(baseEmoji: .revolvingHearts, skinTones: nil), + "💕": EmojiWithSkinTones(baseEmoji: .twoHearts, skinTones: nil), + "💟": EmojiWithSkinTones(baseEmoji: .heartDecoration, skinTones: nil), + "💔": EmojiWithSkinTones(baseEmoji: .brokenHeart, skinTones: nil), + "💛": EmojiWithSkinTones(baseEmoji: .yellowHeart, skinTones: nil), + "💚": EmojiWithSkinTones(baseEmoji: .greenHeart, skinTones: nil), + "💙": EmojiWithSkinTones(baseEmoji: .blueHeart, skinTones: nil), + "💜": EmojiWithSkinTones(baseEmoji: .purpleHeart, skinTones: nil), + "💋": EmojiWithSkinTones(baseEmoji: .kiss, skinTones: nil), + "💯": EmojiWithSkinTones(baseEmoji: .oneHundred, skinTones: nil), + "💢": EmojiWithSkinTones(baseEmoji: .anger, skinTones: nil), + "💥": EmojiWithSkinTones(baseEmoji: .boom, skinTones: nil), + "💫": EmojiWithSkinTones(baseEmoji: .dizzy, skinTones: nil), + "💦": EmojiWithSkinTones(baseEmoji: .sweatDrops, skinTones: nil), + "💨": EmojiWithSkinTones(baseEmoji: .dash, skinTones: nil), + "💬": EmojiWithSkinTones(baseEmoji: .speechBalloon, skinTones: nil), + "💭": EmojiWithSkinTones(baseEmoji: .thoughtBalloon, skinTones: nil), + "💤": EmojiWithSkinTones(baseEmoji: .zzz, skinTones: nil), + "💅": EmojiWithSkinTones(baseEmoji: .nailCare, skinTones: nil), + "💪": EmojiWithSkinTones(baseEmoji: .muscle, skinTones: nil), + "👶": EmojiWithSkinTones(baseEmoji: .baby, skinTones: nil), + "👦": EmojiWithSkinTones(baseEmoji: .boy, skinTones: nil), + "👧": EmojiWithSkinTones(baseEmoji: .girl, skinTones: nil), + "👱": EmojiWithSkinTones(baseEmoji: .personWithBlondHair, skinTones: nil), + "👨": EmojiWithSkinTones(baseEmoji: .man, skinTones: nil), + "👩": EmojiWithSkinTones(baseEmoji: .woman, skinTones: nil), + "👴": EmojiWithSkinTones(baseEmoji: .olderMan, skinTones: nil), + "👵": EmojiWithSkinTones(baseEmoji: .olderWoman, skinTones: nil), + "💁": EmojiWithSkinTones(baseEmoji: .informationDeskPerson, skinTones: nil), + "👮": EmojiWithSkinTones(baseEmoji: .cop, skinTones: nil), + "💂": EmojiWithSkinTones(baseEmoji: .guardsman, skinTones: nil), + "👷": EmojiWithSkinTones(baseEmoji: .constructionWorker, skinTones: nil), + "👸": EmojiWithSkinTones(baseEmoji: .princess, skinTones: nil), + "👳": EmojiWithSkinTones(baseEmoji: .manWithTurban, skinTones: nil), + "👲": EmojiWithSkinTones(baseEmoji: .manWithGuaPiMao, skinTones: nil), + "👰": EmojiWithSkinTones(baseEmoji: .brideWithVeil, skinTones: nil), + "👼": EmojiWithSkinTones(baseEmoji: .angel, skinTones: nil), + "💆": EmojiWithSkinTones(baseEmoji: .massage, skinTones: nil), + "💇": EmojiWithSkinTones(baseEmoji: .haircut, skinTones: nil), + "💃": EmojiWithSkinTones(baseEmoji: .dancer, skinTones: nil), + "👯": EmojiWithSkinTones(baseEmoji: .dancers, skinTones: nil), + "👭": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: nil), + "👫": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: nil), + "👬": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: nil), + "💏": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: nil), + "💑": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: nil), + "👪": EmojiWithSkinTones(baseEmoji: .family, skinTones: nil), + "👤": EmojiWithSkinTones(baseEmoji: .bustInSilhouette, skinTones: nil), + "👥": EmojiWithSkinTones(baseEmoji: .bustsInSilhouette, skinTones: nil), + "💐": EmojiWithSkinTones(baseEmoji: .bouquet, skinTones: nil), + "💮": EmojiWithSkinTones(baseEmoji: .whiteFlower, skinTones: nil), + "💒": EmojiWithSkinTones(baseEmoji: .wedding, skinTones: nil), + "💈": EmojiWithSkinTones(baseEmoji: .barber, skinTones: nil), + "💺": EmojiWithSkinTones(baseEmoji: .seat, skinTones: nil), + "💧": EmojiWithSkinTones(baseEmoji: .droplet, skinTones: nil), + "💄": EmojiWithSkinTones(baseEmoji: .lipstick, skinTones: nil), + "💍": EmojiWithSkinTones(baseEmoji: .ring, skinTones: nil), + "💎": EmojiWithSkinTones(baseEmoji: .gem, skinTones: nil), + "💻": EmojiWithSkinTones(baseEmoji: .computer, skinTones: nil), + "💽": EmojiWithSkinTones(baseEmoji: .minidisc, skinTones: nil), + "💾": EmojiWithSkinTones(baseEmoji: .floppyDisk, skinTones: nil), + "💿": EmojiWithSkinTones(baseEmoji: .cd, skinTones: nil), + "📀": EmojiWithSkinTones(baseEmoji: .dvd, skinTones: nil), + "💡": EmojiWithSkinTones(baseEmoji: .bulb, skinTones: nil), + "📃": EmojiWithSkinTones(baseEmoji: .pageWithCurl, skinTones: nil), + "📄": EmojiWithSkinTones(baseEmoji: .pageFacingUp, skinTones: nil), + "💰": EmojiWithSkinTones(baseEmoji: .moneybag, skinTones: nil), + "💴": EmojiWithSkinTones(baseEmoji: .yen, skinTones: nil), + "💵": EmojiWithSkinTones(baseEmoji: .dollar, skinTones: nil), + "💶": EmojiWithSkinTones(baseEmoji: .euro, skinTones: nil), + "💷": EmojiWithSkinTones(baseEmoji: .pound, skinTones: nil), + "💸": EmojiWithSkinTones(baseEmoji: .moneyWithWings, skinTones: nil), + "💳": EmojiWithSkinTones(baseEmoji: .creditCard, skinTones: nil), + "💹": EmojiWithSkinTones(baseEmoji: .chart, skinTones: nil), + "💼": EmojiWithSkinTones(baseEmoji: .briefcase, skinTones: nil), + "📁": EmojiWithSkinTones(baseEmoji: .fileFolder, skinTones: nil), + "📂": EmojiWithSkinTones(baseEmoji: .openFileFolder, skinTones: nil), + "📅": EmojiWithSkinTones(baseEmoji: .date, skinTones: nil), + "📆": EmojiWithSkinTones(baseEmoji: .calendar, skinTones: nil), + "📇": EmojiWithSkinTones(baseEmoji: .cardIndex, skinTones: nil), + "💣": EmojiWithSkinTones(baseEmoji: .bomb, skinTones: nil), + "💉": EmojiWithSkinTones(baseEmoji: .syringe, skinTones: nil), + "💊": EmojiWithSkinTones(baseEmoji: .pill, skinTones: nil), + "💱": EmojiWithSkinTones(baseEmoji: .currencyExchange, skinTones: nil), + "💲": EmojiWithSkinTones(baseEmoji: .heavyDollarSign, skinTones: nil), + "💠": EmojiWithSkinTones(baseEmoji: .diamondShapeWithADotInside, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1282(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🔪": EmojiWithSkinTones(baseEmoji: .hocho, skinTones: nil), + "🔥": EmojiWithSkinTones(baseEmoji: .fire, skinTones: nil), + "🔫": EmojiWithSkinTones(baseEmoji: .gun, skinTones: nil), + "📿": EmojiWithSkinTones(baseEmoji: .prayerBeads, skinTones: nil), + "🔇": EmojiWithSkinTones(baseEmoji: .mute, skinTones: nil), + "🔈": EmojiWithSkinTones(baseEmoji: .speaker, skinTones: nil), + "🔉": EmojiWithSkinTones(baseEmoji: .sound, skinTones: nil), + "🔊": EmojiWithSkinTones(baseEmoji: .loudSound, skinTones: nil), + "📢": EmojiWithSkinTones(baseEmoji: .loudspeaker, skinTones: nil), + "📣": EmojiWithSkinTones(baseEmoji: .mega, skinTones: nil), + "📯": EmojiWithSkinTones(baseEmoji: .postalHorn, skinTones: nil), + "🔔": EmojiWithSkinTones(baseEmoji: .bell, skinTones: nil), + "🔕": EmojiWithSkinTones(baseEmoji: .noBell, skinTones: nil), + "📻": EmojiWithSkinTones(baseEmoji: .radio, skinTones: nil), + "📱": EmojiWithSkinTones(baseEmoji: .iphone, skinTones: nil), + "📲": EmojiWithSkinTones(baseEmoji: .calling, skinTones: nil), + "📞": EmojiWithSkinTones(baseEmoji: .telephoneReceiver, skinTones: nil), + "📟": EmojiWithSkinTones(baseEmoji: .pager, skinTones: nil), + "📠": EmojiWithSkinTones(baseEmoji: .fax, skinTones: nil), + "🔋": EmojiWithSkinTones(baseEmoji: .battery, skinTones: nil), + "🔌": EmojiWithSkinTones(baseEmoji: .electricPlug, skinTones: nil), + "📺": EmojiWithSkinTones(baseEmoji: .tv, skinTones: nil), + "📷": EmojiWithSkinTones(baseEmoji: .camera, skinTones: nil), + "📸": EmojiWithSkinTones(baseEmoji: .cameraWithFlash, skinTones: nil), + "📹": EmojiWithSkinTones(baseEmoji: .videoCamera, skinTones: nil), + "📼": EmojiWithSkinTones(baseEmoji: .vhs, skinTones: nil), + "🔍": EmojiWithSkinTones(baseEmoji: .mag, skinTones: nil), + "🔎": EmojiWithSkinTones(baseEmoji: .magRight, skinTones: nil), + "🔦": EmojiWithSkinTones(baseEmoji: .flashlight, skinTones: nil), + "📔": EmojiWithSkinTones(baseEmoji: .notebookWithDecorativeCover, skinTones: nil), + "📕": EmojiWithSkinTones(baseEmoji: .closedBook, skinTones: nil), + "📖": EmojiWithSkinTones(baseEmoji: .book, skinTones: nil), + "📗": EmojiWithSkinTones(baseEmoji: .greenBook, skinTones: nil), + "📘": EmojiWithSkinTones(baseEmoji: .blueBook, skinTones: nil), + "📙": EmojiWithSkinTones(baseEmoji: .orangeBook, skinTones: nil), + "📚": EmojiWithSkinTones(baseEmoji: .books, skinTones: nil), + "📓": EmojiWithSkinTones(baseEmoji: .notebook, skinTones: nil), + "📒": EmojiWithSkinTones(baseEmoji: .ledger, skinTones: nil), + "📜": EmojiWithSkinTones(baseEmoji: .scroll, skinTones: nil), + "📰": EmojiWithSkinTones(baseEmoji: .newspaper, skinTones: nil), + "📑": EmojiWithSkinTones(baseEmoji: .bookmarkTabs, skinTones: nil), + "🔖": EmojiWithSkinTones(baseEmoji: .bookmark, skinTones: nil), + "📧": EmojiWithSkinTones(baseEmoji: .eMail, skinTones: nil), + "📨": EmojiWithSkinTones(baseEmoji: .incomingEnvelope, skinTones: nil), + "📩": EmojiWithSkinTones(baseEmoji: .envelopeWithArrow, skinTones: nil), + "📤": EmojiWithSkinTones(baseEmoji: .outboxTray, skinTones: nil), + "📥": EmojiWithSkinTones(baseEmoji: .inboxTray, skinTones: nil), + "📦": EmojiWithSkinTones(baseEmoji: .package, skinTones: nil), + "📫": EmojiWithSkinTones(baseEmoji: .mailbox, skinTones: nil), + "📪": EmojiWithSkinTones(baseEmoji: .mailboxClosed, skinTones: nil), + "📬": EmojiWithSkinTones(baseEmoji: .mailboxWithMail, skinTones: nil), + "📭": EmojiWithSkinTones(baseEmoji: .mailboxWithNoMail, skinTones: nil), + "📮": EmojiWithSkinTones(baseEmoji: .postbox, skinTones: nil), + "📝": EmojiWithSkinTones(baseEmoji: .memo, skinTones: nil), + "📈": EmojiWithSkinTones(baseEmoji: .chartWithUpwardsTrend, skinTones: nil), + "📉": EmojiWithSkinTones(baseEmoji: .chartWithDownwardsTrend, skinTones: nil), + "📊": EmojiWithSkinTones(baseEmoji: .barChart, skinTones: nil), + "📋": EmojiWithSkinTones(baseEmoji: .clipboard, skinTones: nil), + "📌": EmojiWithSkinTones(baseEmoji: .pushpin, skinTones: nil), + "📍": EmojiWithSkinTones(baseEmoji: .roundPushpin, skinTones: nil), + "📎": EmojiWithSkinTones(baseEmoji: .paperclip, skinTones: nil), + "📏": EmojiWithSkinTones(baseEmoji: .straightRuler, skinTones: nil), + "📐": EmojiWithSkinTones(baseEmoji: .triangularRuler, skinTones: nil), + "🔒": EmojiWithSkinTones(baseEmoji: .lock, skinTones: nil), + "🔓": EmojiWithSkinTones(baseEmoji: .unlock, skinTones: nil), + "🔏": EmojiWithSkinTones(baseEmoji: .lockWithInkPen, skinTones: nil), + "🔐": EmojiWithSkinTones(baseEmoji: .closedLockWithKey, skinTones: nil), + "🔑": EmojiWithSkinTones(baseEmoji: .key, skinTones: nil), + "🔨": EmojiWithSkinTones(baseEmoji: .hammer, skinTones: nil), + "🔧": EmojiWithSkinTones(baseEmoji: .wrench, skinTones: nil), + "🔩": EmojiWithSkinTones(baseEmoji: .nutAndBolt, skinTones: nil), + "🔗": EmojiWithSkinTones(baseEmoji: .link, skinTones: nil), + "📡": EmojiWithSkinTones(baseEmoji: .satelliteAntenna, skinTones: nil), + "📵": EmojiWithSkinTones(baseEmoji: .noMobilePhones, skinTones: nil), + "🔞": EmojiWithSkinTones(baseEmoji: .underage, skinTones: nil), + "🔃": EmojiWithSkinTones(baseEmoji: .arrowsClockwise, skinTones: nil), + "🔄": EmojiWithSkinTones(baseEmoji: .arrowsCounterclockwise, skinTones: nil), + "🔙": EmojiWithSkinTones(baseEmoji: .back, skinTones: nil), + "🔚": EmojiWithSkinTones(baseEmoji: .end, skinTones: nil), + "🔛": EmojiWithSkinTones(baseEmoji: .on, skinTones: nil), + "🔜": EmojiWithSkinTones(baseEmoji: .soon, skinTones: nil), + "🔝": EmojiWithSkinTones(baseEmoji: .top, skinTones: nil), + "🔀": EmojiWithSkinTones(baseEmoji: .twistedRightwardsArrows, skinTones: nil), + "🔁": EmojiWithSkinTones(baseEmoji: .`repeat`, skinTones: nil), + "🔂": EmojiWithSkinTones(baseEmoji: .repeatOne, skinTones: nil), + "🔅": EmojiWithSkinTones(baseEmoji: .lowBrightness, skinTones: nil), + "🔆": EmojiWithSkinTones(baseEmoji: .highBrightness, skinTones: nil), + "📶": EmojiWithSkinTones(baseEmoji: .signalStrength, skinTones: nil), + "📳": EmojiWithSkinTones(baseEmoji: .vibrationMode, skinTones: nil), + "📴": EmojiWithSkinTones(baseEmoji: .mobilePhoneOff, skinTones: nil), + "📛": EmojiWithSkinTones(baseEmoji: .nameBadge, skinTones: nil), + "🔟": EmojiWithSkinTones(baseEmoji: .keycapTen, skinTones: nil), + "🔠": EmojiWithSkinTones(baseEmoji: .capitalAbcd, skinTones: nil), + "🔡": EmojiWithSkinTones(baseEmoji: .abcd, skinTones: nil), + "🔢": EmojiWithSkinTones(baseEmoji: .oneTwoThreeFour, skinTones: nil), + "🔣": EmojiWithSkinTones(baseEmoji: .symbols, skinTones: nil), + "🔤": EmojiWithSkinTones(baseEmoji: .abc, skinTones: nil), + "🔘": EmojiWithSkinTones(baseEmoji: .radioButton, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1283(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🕺": EmojiWithSkinTones(baseEmoji: .manDancing, skinTones: nil), + "🕌": EmojiWithSkinTones(baseEmoji: .mosque, skinTones: nil), + "🕍": EmojiWithSkinTones(baseEmoji: .synagogue, skinTones: nil), + "🕋": EmojiWithSkinTones(baseEmoji: .kaaba, skinTones: nil), + "🕛": EmojiWithSkinTones(baseEmoji: .clock12, skinTones: nil), + "🕧": EmojiWithSkinTones(baseEmoji: .clock1230, skinTones: nil), + "🕐": EmojiWithSkinTones(baseEmoji: .clock1, skinTones: nil), + "🕜": EmojiWithSkinTones(baseEmoji: .clock130, skinTones: nil), + "🕑": EmojiWithSkinTones(baseEmoji: .clock2, skinTones: nil), + "🕝": EmojiWithSkinTones(baseEmoji: .clock230, skinTones: nil), + "🕒": EmojiWithSkinTones(baseEmoji: .clock3, skinTones: nil), + "🕞": EmojiWithSkinTones(baseEmoji: .clock330, skinTones: nil), + "🕓": EmojiWithSkinTones(baseEmoji: .clock4, skinTones: nil), + "🕟": EmojiWithSkinTones(baseEmoji: .clock430, skinTones: nil), + "🕔": EmojiWithSkinTones(baseEmoji: .clock5, skinTones: nil), + "🕠": EmojiWithSkinTones(baseEmoji: .clock530, skinTones: nil), + "🕕": EmojiWithSkinTones(baseEmoji: .clock6, skinTones: nil), + "🕡": EmojiWithSkinTones(baseEmoji: .clock630, skinTones: nil), + "🕖": EmojiWithSkinTones(baseEmoji: .clock7, skinTones: nil), + "🕢": EmojiWithSkinTones(baseEmoji: .clock730, skinTones: nil), + "🕗": EmojiWithSkinTones(baseEmoji: .clock8, skinTones: nil), + "🕣": EmojiWithSkinTones(baseEmoji: .clock830, skinTones: nil), + "🕘": EmojiWithSkinTones(baseEmoji: .clock9, skinTones: nil), + "🕤": EmojiWithSkinTones(baseEmoji: .clock930, skinTones: nil), + "🕙": EmojiWithSkinTones(baseEmoji: .clock10, skinTones: nil), + "🕥": EmojiWithSkinTones(baseEmoji: .clock1030, skinTones: nil), + "🕚": EmojiWithSkinTones(baseEmoji: .clock11, skinTones: nil), + "🕦": EmojiWithSkinTones(baseEmoji: .clock1130, skinTones: nil), + "🔮": EmojiWithSkinTones(baseEmoji: .crystalBall, skinTones: nil), + "🔬": EmojiWithSkinTones(baseEmoji: .microscope, skinTones: nil), + "🔭": EmojiWithSkinTones(baseEmoji: .telescope, skinTones: nil), + "🕎": EmojiWithSkinTones(baseEmoji: .menorahWithNineBranches, skinTones: nil), + "🔯": EmojiWithSkinTones(baseEmoji: .sixPointedStar, skinTones: nil), + "🔼": EmojiWithSkinTones(baseEmoji: .arrowUpSmall, skinTones: nil), + "🔽": EmojiWithSkinTones(baseEmoji: .arrowDownSmall, skinTones: nil), + "🔱": EmojiWithSkinTones(baseEmoji: .trident, skinTones: nil), + "🔰": EmojiWithSkinTones(baseEmoji: .beginner, skinTones: nil), + "🔴": EmojiWithSkinTones(baseEmoji: .redCircle, skinTones: nil), + "🔵": EmojiWithSkinTones(baseEmoji: .largeBlueCircle, skinTones: nil), + "🔶": EmojiWithSkinTones(baseEmoji: .largeOrangeDiamond, skinTones: nil), + "🔷": EmojiWithSkinTones(baseEmoji: .largeBlueDiamond, skinTones: nil), + "🔸": EmojiWithSkinTones(baseEmoji: .smallOrangeDiamond, skinTones: nil), + "🔹": EmojiWithSkinTones(baseEmoji: .smallBlueDiamond, skinTones: nil), + "🔺": EmojiWithSkinTones(baseEmoji: .smallRedTriangle, skinTones: nil), + "🔻": EmojiWithSkinTones(baseEmoji: .smallRedTriangleDown, skinTones: nil), + "🔳": EmojiWithSkinTones(baseEmoji: .whiteSquareButton, skinTones: nil), + "🔲": EmojiWithSkinTones(baseEmoji: .blackSquareButton, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1284(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🖤": EmojiWithSkinTones(baseEmoji: .blackHeart, skinTones: nil), + "🖖": EmojiWithSkinTones(baseEmoji: .spockHand, skinTones: nil), + "🖕": EmojiWithSkinTones(baseEmoji: .middleFinger, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1285(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "😀": EmojiWithSkinTones(baseEmoji: .grinning, skinTones: nil), + "😃": EmojiWithSkinTones(baseEmoji: .smiley, skinTones: nil), + "😄": EmojiWithSkinTones(baseEmoji: .smile, skinTones: nil), + "😁": EmojiWithSkinTones(baseEmoji: .grin, skinTones: nil), + "😆": EmojiWithSkinTones(baseEmoji: .laughing, skinTones: nil), + "😅": EmojiWithSkinTones(baseEmoji: .sweatSmile, skinTones: nil), + "😂": EmojiWithSkinTones(baseEmoji: .joy, skinTones: nil), + "🙂": EmojiWithSkinTones(baseEmoji: .slightlySmilingFace, skinTones: nil), + "🙃": EmojiWithSkinTones(baseEmoji: .upsideDownFace, skinTones: nil), + "😉": EmojiWithSkinTones(baseEmoji: .wink, skinTones: nil), + "😊": EmojiWithSkinTones(baseEmoji: .blush, skinTones: nil), + "😇": EmojiWithSkinTones(baseEmoji: .innocent, skinTones: nil), + "😍": EmojiWithSkinTones(baseEmoji: .heartEyes, skinTones: nil), + "😘": EmojiWithSkinTones(baseEmoji: .kissingHeart, skinTones: nil), + "😗": EmojiWithSkinTones(baseEmoji: .kissing, skinTones: nil), + "😚": EmojiWithSkinTones(baseEmoji: .kissingClosedEyes, skinTones: nil), + "😙": EmojiWithSkinTones(baseEmoji: .kissingSmilingEyes, skinTones: nil), + "😋": EmojiWithSkinTones(baseEmoji: .yum, skinTones: nil), + "😛": EmojiWithSkinTones(baseEmoji: .stuckOutTongue, skinTones: nil), + "😜": EmojiWithSkinTones(baseEmoji: .stuckOutTongueWinkingEye, skinTones: nil), + "😝": EmojiWithSkinTones(baseEmoji: .stuckOutTongueClosedEyes, skinTones: nil), + "😐": EmojiWithSkinTones(baseEmoji: .neutralFace, skinTones: nil), + "😑": EmojiWithSkinTones(baseEmoji: .expressionless, skinTones: nil), + "😶": EmojiWithSkinTones(baseEmoji: .noMouth, skinTones: nil), + "😏": EmojiWithSkinTones(baseEmoji: .smirk, skinTones: nil), + "😒": EmojiWithSkinTones(baseEmoji: .unamused, skinTones: nil), + "🙄": EmojiWithSkinTones(baseEmoji: .faceWithRollingEyes, skinTones: nil), + "😬": EmojiWithSkinTones(baseEmoji: .grimacing, skinTones: nil), + "😌": EmojiWithSkinTones(baseEmoji: .relieved, skinTones: nil), + "😔": EmojiWithSkinTones(baseEmoji: .pensive, skinTones: nil), + "😪": EmojiWithSkinTones(baseEmoji: .sleepy, skinTones: nil), + "😴": EmojiWithSkinTones(baseEmoji: .sleeping, skinTones: nil), + "😷": EmojiWithSkinTones(baseEmoji: .mask, skinTones: nil), + "😵": EmojiWithSkinTones(baseEmoji: .dizzyFace, skinTones: nil), + "😎": EmojiWithSkinTones(baseEmoji: .sunglasses, skinTones: nil), + "😕": EmojiWithSkinTones(baseEmoji: .confused, skinTones: nil), + "😟": EmojiWithSkinTones(baseEmoji: .worried, skinTones: nil), + "🙁": EmojiWithSkinTones(baseEmoji: .slightlyFrowningFace, skinTones: nil), + "😮": EmojiWithSkinTones(baseEmoji: .openMouth, skinTones: nil), + "😯": EmojiWithSkinTones(baseEmoji: .hushed, skinTones: nil), + "😲": EmojiWithSkinTones(baseEmoji: .astonished, skinTones: nil), + "😳": EmojiWithSkinTones(baseEmoji: .flushed, skinTones: nil), + "😦": EmojiWithSkinTones(baseEmoji: .frowning, skinTones: nil), + "😧": EmojiWithSkinTones(baseEmoji: .anguished, skinTones: nil), + "😨": EmojiWithSkinTones(baseEmoji: .fearful, skinTones: nil), + "😰": EmojiWithSkinTones(baseEmoji: .coldSweat, skinTones: nil), + "😥": EmojiWithSkinTones(baseEmoji: .disappointedRelieved, skinTones: nil), + "😢": EmojiWithSkinTones(baseEmoji: .cry, skinTones: nil), + "😭": EmojiWithSkinTones(baseEmoji: .sob, skinTones: nil), + "😱": EmojiWithSkinTones(baseEmoji: .scream, skinTones: nil), + "😖": EmojiWithSkinTones(baseEmoji: .confounded, skinTones: nil), + "😣": EmojiWithSkinTones(baseEmoji: .persevere, skinTones: nil), + "😞": EmojiWithSkinTones(baseEmoji: .disappointed, skinTones: nil), + "😓": EmojiWithSkinTones(baseEmoji: .sweat, skinTones: nil), + "😩": EmojiWithSkinTones(baseEmoji: .weary, skinTones: nil), + "😫": EmojiWithSkinTones(baseEmoji: .tiredFace, skinTones: nil), + "😤": EmojiWithSkinTones(baseEmoji: .triumph, skinTones: nil), + "😡": EmojiWithSkinTones(baseEmoji: .rage, skinTones: nil), + "😠": EmojiWithSkinTones(baseEmoji: .angry, skinTones: nil), + "😈": EmojiWithSkinTones(baseEmoji: .smilingImp, skinTones: nil), + "😺": EmojiWithSkinTones(baseEmoji: .smileyCat, skinTones: nil), + "😸": EmojiWithSkinTones(baseEmoji: .smileCat, skinTones: nil), + "😹": EmojiWithSkinTones(baseEmoji: .joyCat, skinTones: nil), + "😻": EmojiWithSkinTones(baseEmoji: .heartEyesCat, skinTones: nil), + "😼": EmojiWithSkinTones(baseEmoji: .smirkCat, skinTones: nil), + "😽": EmojiWithSkinTones(baseEmoji: .kissingCat, skinTones: nil), + "🙀": EmojiWithSkinTones(baseEmoji: .screamCat, skinTones: nil), + "😿": EmojiWithSkinTones(baseEmoji: .cryingCatFace, skinTones: nil), + "😾": EmojiWithSkinTones(baseEmoji: .poutingCat, skinTones: nil), + "🙈": EmojiWithSkinTones(baseEmoji: .seeNoEvil, skinTones: nil), + "🙉": EmojiWithSkinTones(baseEmoji: .hearNoEvil, skinTones: nil), + "🙊": EmojiWithSkinTones(baseEmoji: .speakNoEvil, skinTones: nil), + "🙌": EmojiWithSkinTones(baseEmoji: .raisedHands, skinTones: nil), + "🙏": EmojiWithSkinTones(baseEmoji: .pray, skinTones: nil), + "🙍": EmojiWithSkinTones(baseEmoji: .personFrowning, skinTones: nil), + "🙎": EmojiWithSkinTones(baseEmoji: .personWithPoutingFace, skinTones: nil), + "🙅": EmojiWithSkinTones(baseEmoji: .noGood, skinTones: nil), + "🙆": EmojiWithSkinTones(baseEmoji: .okWoman, skinTones: nil), + "🙋": EmojiWithSkinTones(baseEmoji: .raisingHand, skinTones: nil), + "🙇": EmojiWithSkinTones(baseEmoji: .bow, skinTones: nil), + "🗾": EmojiWithSkinTones(baseEmoji: .japan, skinTones: nil), + "🗻": EmojiWithSkinTones(baseEmoji: .mountFuji, skinTones: nil), + "🗼": EmojiWithSkinTones(baseEmoji: .tokyoTower, skinTones: nil), + "🗽": EmojiWithSkinTones(baseEmoji: .statueOfLiberty, skinTones: nil), + "🗿": EmojiWithSkinTones(baseEmoji: .moyai, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1286(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🚶": EmojiWithSkinTones(baseEmoji: .walking, skinTones: nil), + "🚣": EmojiWithSkinTones(baseEmoji: .rowboat, skinTones: nil), + "🚴": EmojiWithSkinTones(baseEmoji: .bicyclist, skinTones: nil), + "🚵": EmojiWithSkinTones(baseEmoji: .mountainBicyclist, skinTones: nil), + "🚂": EmojiWithSkinTones(baseEmoji: .steamLocomotive, skinTones: nil), + "🚃": EmojiWithSkinTones(baseEmoji: .railwayCar, skinTones: nil), + "🚄": EmojiWithSkinTones(baseEmoji: .bullettrainSide, skinTones: nil), + "🚅": EmojiWithSkinTones(baseEmoji: .bullettrainFront, skinTones: nil), + "🚆": EmojiWithSkinTones(baseEmoji: .train2, skinTones: nil), + "🚇": EmojiWithSkinTones(baseEmoji: .metro, skinTones: nil), + "🚈": EmojiWithSkinTones(baseEmoji: .lightRail, skinTones: nil), + "🚉": EmojiWithSkinTones(baseEmoji: .station, skinTones: nil), + "🚊": EmojiWithSkinTones(baseEmoji: .tram, skinTones: nil), + "🚝": EmojiWithSkinTones(baseEmoji: .monorail, skinTones: nil), + "🚞": EmojiWithSkinTones(baseEmoji: .mountainRailway, skinTones: nil), + "🚋": EmojiWithSkinTones(baseEmoji: .train, skinTones: nil), + "🚌": EmojiWithSkinTones(baseEmoji: .bus, skinTones: nil), + "🚍": EmojiWithSkinTones(baseEmoji: .oncomingBus, skinTones: nil), + "🚎": EmojiWithSkinTones(baseEmoji: .trolleybus, skinTones: nil), + "🚐": EmojiWithSkinTones(baseEmoji: .minibus, skinTones: nil), + "🚑": EmojiWithSkinTones(baseEmoji: .ambulance, skinTones: nil), + "🚒": EmojiWithSkinTones(baseEmoji: .fireEngine, skinTones: nil), + "🚓": EmojiWithSkinTones(baseEmoji: .policeCar, skinTones: nil), + "🚔": EmojiWithSkinTones(baseEmoji: .oncomingPoliceCar, skinTones: nil), + "🚕": EmojiWithSkinTones(baseEmoji: .taxi, skinTones: nil), + "🚖": EmojiWithSkinTones(baseEmoji: .oncomingTaxi, skinTones: nil), + "🚗": EmojiWithSkinTones(baseEmoji: .car, skinTones: nil), + "🚘": EmojiWithSkinTones(baseEmoji: .oncomingAutomobile, skinTones: nil), + "🚙": EmojiWithSkinTones(baseEmoji: .blueCar, skinTones: nil), + "🚚": EmojiWithSkinTones(baseEmoji: .truck, skinTones: nil), + "🚛": EmojiWithSkinTones(baseEmoji: .articulatedLorry, skinTones: nil), + "🚜": EmojiWithSkinTones(baseEmoji: .tractor, skinTones: nil), + "🚲": EmojiWithSkinTones(baseEmoji: .bike, skinTones: nil), + "🚏": EmojiWithSkinTones(baseEmoji: .busstop, skinTones: nil), + "🚨": EmojiWithSkinTones(baseEmoji: .rotatingLight, skinTones: nil), + "🚥": EmojiWithSkinTones(baseEmoji: .trafficLight, skinTones: nil), + "🚦": EmojiWithSkinTones(baseEmoji: .verticalTrafficLight, skinTones: nil), + "🚧": EmojiWithSkinTones(baseEmoji: .construction, skinTones: nil), + "🚤": EmojiWithSkinTones(baseEmoji: .speedboat, skinTones: nil), + "🚢": EmojiWithSkinTones(baseEmoji: .ship, skinTones: nil), + "🚁": EmojiWithSkinTones(baseEmoji: .helicopter, skinTones: nil), + "🚟": EmojiWithSkinTones(baseEmoji: .suspensionRailway, skinTones: nil), + "🚠": EmojiWithSkinTones(baseEmoji: .mountainCableway, skinTones: nil), + "🚡": EmojiWithSkinTones(baseEmoji: .aerialTramway, skinTones: nil), + "🚀": EmojiWithSkinTones(baseEmoji: .rocket, skinTones: nil), + "🚪": EmojiWithSkinTones(baseEmoji: .door, skinTones: nil), + "🚬": EmojiWithSkinTones(baseEmoji: .smoking, skinTones: nil), + "🚮": EmojiWithSkinTones(baseEmoji: .putLitterInItsPlace, skinTones: nil), + "🚰": EmojiWithSkinTones(baseEmoji: .potableWater, skinTones: nil), + "🚹": EmojiWithSkinTones(baseEmoji: .mens, skinTones: nil), + "🚺": EmojiWithSkinTones(baseEmoji: .womens, skinTones: nil), + "🚻": EmojiWithSkinTones(baseEmoji: .restroom, skinTones: nil), + "🚸": EmojiWithSkinTones(baseEmoji: .childrenCrossing, skinTones: nil), + "🚫": EmojiWithSkinTones(baseEmoji: .noEntrySign, skinTones: nil), + "🚳": EmojiWithSkinTones(baseEmoji: .noBicycles, skinTones: nil), + "🚭": EmojiWithSkinTones(baseEmoji: .noSmoking, skinTones: nil), + "🚯": EmojiWithSkinTones(baseEmoji: .doNotLitter, skinTones: nil), + "🚱": EmojiWithSkinTones(baseEmoji: .nonPotableWater, skinTones: nil), + "🚷": EmojiWithSkinTones(baseEmoji: .noPedestrians, skinTones: nil), + "🚩": EmojiWithSkinTones(baseEmoji: .triangularFlagOnPost, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1287(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🛀": EmojiWithSkinTones(baseEmoji: .bath, skinTones: nil), + "🛌": EmojiWithSkinTones(baseEmoji: .sleepingAccommodation, skinTones: nil), + "🛖": EmojiWithSkinTones(baseEmoji: .hut, skinTones: nil), + "🛕": EmojiWithSkinTones(baseEmoji: .hinduTemple, skinTones: nil), + "🛝": EmojiWithSkinTones(baseEmoji: .playgroundSlide, skinTones: nil), + "🛻": EmojiWithSkinTones(baseEmoji: .pickupTruck, skinTones: nil), + "🛵": EmojiWithSkinTones(baseEmoji: .motorScooter, skinTones: nil), + "🛺": EmojiWithSkinTones(baseEmoji: .autoRickshaw, skinTones: nil), + "🛴": EmojiWithSkinTones(baseEmoji: .scooter, skinTones: nil), + "🛹": EmojiWithSkinTones(baseEmoji: .skateboard, skinTones: nil), + "🛼": EmojiWithSkinTones(baseEmoji: .rollerSkate, skinTones: nil), + "🛞": EmojiWithSkinTones(baseEmoji: .wheel, skinTones: nil), + "🛑": EmojiWithSkinTones(baseEmoji: .octagonalSign, skinTones: nil), + "🛟": EmojiWithSkinTones(baseEmoji: .ringBuoy, skinTones: nil), + "🛶": EmojiWithSkinTones(baseEmoji: .canoe, skinTones: nil), + "🛫": EmojiWithSkinTones(baseEmoji: .airplaneDeparture, skinTones: nil), + "🛬": EmojiWithSkinTones(baseEmoji: .airplaneArriving, skinTones: nil), + "🛸": EmojiWithSkinTones(baseEmoji: .flyingSaucer, skinTones: nil), + "🛷": EmojiWithSkinTones(baseEmoji: .sled, skinTones: nil), + "🛗": EmojiWithSkinTones(baseEmoji: .elevator, skinTones: nil), + "🚽": EmojiWithSkinTones(baseEmoji: .toilet, skinTones: nil), + "🚿": EmojiWithSkinTones(baseEmoji: .shower, skinTones: nil), + "🛁": EmojiWithSkinTones(baseEmoji: .bathtub, skinTones: nil), + "🛒": EmojiWithSkinTones(baseEmoji: .shoppingTrolley, skinTones: nil), + "🚼": EmojiWithSkinTones(baseEmoji: .babySymbol, skinTones: nil), + "🚾": EmojiWithSkinTones(baseEmoji: .wc, skinTones: nil), + "🛂": EmojiWithSkinTones(baseEmoji: .passportControl, skinTones: nil), + "🛃": EmojiWithSkinTones(baseEmoji: .customs, skinTones: nil), + "🛄": EmojiWithSkinTones(baseEmoji: .baggageClaim, skinTones: nil), + "🛅": EmojiWithSkinTones(baseEmoji: .leftLuggage, skinTones: nil), + "🛐": EmojiWithSkinTones(baseEmoji: .placeOfWorship, skinTones: nil), + "🛜": EmojiWithSkinTones(baseEmoji: .wireless, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1289(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🟠": EmojiWithSkinTones(baseEmoji: .largeOrangeCircle, skinTones: nil), + "🟡": EmojiWithSkinTones(baseEmoji: .largeYellowCircle, skinTones: nil), + "🟢": EmojiWithSkinTones(baseEmoji: .largeGreenCircle, skinTones: nil), + "🟣": EmojiWithSkinTones(baseEmoji: .largePurpleCircle, skinTones: nil), + "🟤": EmojiWithSkinTones(baseEmoji: .largeBrownCircle, skinTones: nil), + "🟥": EmojiWithSkinTones(baseEmoji: .largeRedSquare, skinTones: nil), + "🟧": EmojiWithSkinTones(baseEmoji: .largeOrangeSquare, skinTones: nil), + "🟦": EmojiWithSkinTones(baseEmoji: .largeBlueSquare, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1290(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🟰": EmojiWithSkinTones(baseEmoji: .heavyEqualsSign, skinTones: nil), + "🟨": EmojiWithSkinTones(baseEmoji: .largeYellowSquare, skinTones: nil), + "🟩": EmojiWithSkinTones(baseEmoji: .largeGreenSquare, skinTones: nil), + "🟪": EmojiWithSkinTones(baseEmoji: .largePurpleSquare, skinTones: nil), + "🟫": EmojiWithSkinTones(baseEmoji: .largeBrownSquare, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1292(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🤑": EmojiWithSkinTones(baseEmoji: .moneyMouthFace, skinTones: nil), + "🤐": EmojiWithSkinTones(baseEmoji: .zipperMouthFace, skinTones: nil), + "🤒": EmojiWithSkinTones(baseEmoji: .faceWithThermometer, skinTones: nil), + "🤓": EmojiWithSkinTones(baseEmoji: .nerdFace, skinTones: nil), + "🤎": EmojiWithSkinTones(baseEmoji: .brownHeart, skinTones: nil), + "🤍": EmojiWithSkinTones(baseEmoji: .whiteHeart, skinTones: nil), + "🤌": EmojiWithSkinTones(baseEmoji: .pinchedFingers, skinTones: nil), + "🤏": EmojiWithSkinTones(baseEmoji: .pinchingHand, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1293(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🤣": EmojiWithSkinTones(baseEmoji: .rollingOnTheFloorLaughing, skinTones: nil), + "🥰": EmojiWithSkinTones(baseEmoji: .smilingFaceWith3Hearts, skinTones: nil), + "🤩": EmojiWithSkinTones(baseEmoji: .starStruck, skinTones: nil), + "🥲": EmojiWithSkinTones(baseEmoji: .smilingFaceWithTear, skinTones: nil), + "🤪": EmojiWithSkinTones(baseEmoji: .zanyFace, skinTones: nil), + "🤗": EmojiWithSkinTones(baseEmoji: .huggingFace, skinTones: nil), + "🤭": EmojiWithSkinTones(baseEmoji: .faceWithHandOverMouth, skinTones: nil), + "🤫": EmojiWithSkinTones(baseEmoji: .shushingFace, skinTones: nil), + "🤔": EmojiWithSkinTones(baseEmoji: .thinkingFace, skinTones: nil), + "🤨": EmojiWithSkinTones(baseEmoji: .faceWithRaisedEyebrow, skinTones: nil), + "🤥": EmojiWithSkinTones(baseEmoji: .lyingFace, skinTones: nil), + "🤤": EmojiWithSkinTones(baseEmoji: .droolingFace, skinTones: nil), + "🤕": EmojiWithSkinTones(baseEmoji: .faceWithHeadBandage, skinTones: nil), + "🤢": EmojiWithSkinTones(baseEmoji: .nauseatedFace, skinTones: nil), + "🤮": EmojiWithSkinTones(baseEmoji: .faceVomiting, skinTones: nil), + "🤧": EmojiWithSkinTones(baseEmoji: .sneezingFace, skinTones: nil), + "🥵": EmojiWithSkinTones(baseEmoji: .hotFace, skinTones: nil), + "🥶": EmojiWithSkinTones(baseEmoji: .coldFace, skinTones: nil), + "🥴": EmojiWithSkinTones(baseEmoji: .woozyFace, skinTones: nil), + "🤯": EmojiWithSkinTones(baseEmoji: .explodingHead, skinTones: nil), + "🤠": EmojiWithSkinTones(baseEmoji: .faceWithCowboyHat, skinTones: nil), + "🥳": EmojiWithSkinTones(baseEmoji: .partyingFace, skinTones: nil), + "🥱": EmojiWithSkinTones(baseEmoji: .yawningFace, skinTones: nil), + "🤬": EmojiWithSkinTones(baseEmoji: .faceWithSymbolsOnMouth, skinTones: nil), + "🤡": EmojiWithSkinTones(baseEmoji: .clownFace, skinTones: nil), + "🤖": EmojiWithSkinTones(baseEmoji: .robotFace, skinTones: nil), + "🤚": EmojiWithSkinTones(baseEmoji: .raisedBackOfHand, skinTones: nil), + "🤞": EmojiWithSkinTones(baseEmoji: .crossedFingers, skinTones: nil), + "🤟": EmojiWithSkinTones(baseEmoji: .iLoveYouHandSign, skinTones: nil), + "🤘": EmojiWithSkinTones(baseEmoji: .theHorns, skinTones: nil), + "🤙": EmojiWithSkinTones(baseEmoji: .callMeHand, skinTones: nil), + "🤛": EmojiWithSkinTones(baseEmoji: .leftFacingFist, skinTones: nil), + "🤜": EmojiWithSkinTones(baseEmoji: .rightFacingFist, skinTones: nil), + "🤲": EmojiWithSkinTones(baseEmoji: .palmsUpTogether, skinTones: nil), + "🤝": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: nil), + "🤳": EmojiWithSkinTones(baseEmoji: .selfie, skinTones: nil), + "🤦": EmojiWithSkinTones(baseEmoji: .facePalm, skinTones: nil), + "🤷": EmojiWithSkinTones(baseEmoji: .shrug, skinTones: nil), + "🥷": EmojiWithSkinTones(baseEmoji: .ninja, skinTones: nil), + "🤴": EmojiWithSkinTones(baseEmoji: .prince, skinTones: nil), + "🤵": EmojiWithSkinTones(baseEmoji: .personInTuxedo, skinTones: nil), + "🤰": EmojiWithSkinTones(baseEmoji: .pregnantWoman, skinTones: nil), + "🤱": EmojiWithSkinTones(baseEmoji: .breastFeeding, skinTones: nil), + "🤶": EmojiWithSkinTones(baseEmoji: .mrsClaus, skinTones: nil), + "🤺": EmojiWithSkinTones(baseEmoji: .fencer, skinTones: nil), + "🤸": EmojiWithSkinTones(baseEmoji: .personDoingCartwheel, skinTones: nil), + "🤼": EmojiWithSkinTones(baseEmoji: .wrestlers, skinTones: nil), + "🤽": EmojiWithSkinTones(baseEmoji: .waterPolo, skinTones: nil), + "🤾": EmojiWithSkinTones(baseEmoji: .handball, skinTones: nil), + "🤹": EmojiWithSkinTones(baseEmoji: .juggling, skinTones: nil), + "🥀": EmojiWithSkinTones(baseEmoji: .wiltedFlower, skinTones: nil), + "🥭": EmojiWithSkinTones(baseEmoji: .mango, skinTones: nil), + "🥝": EmojiWithSkinTones(baseEmoji: .kiwifruit, skinTones: nil), + "🥥": EmojiWithSkinTones(baseEmoji: .coconut, skinTones: nil), + "🥑": EmojiWithSkinTones(baseEmoji: .avocado, skinTones: nil), + "🥔": EmojiWithSkinTones(baseEmoji: .potato, skinTones: nil), + "🥕": EmojiWithSkinTones(baseEmoji: .carrot, skinTones: nil), + "🥒": EmojiWithSkinTones(baseEmoji: .cucumber, skinTones: nil), + "🥬": EmojiWithSkinTones(baseEmoji: .leafyGreen, skinTones: nil), + "🥦": EmojiWithSkinTones(baseEmoji: .broccoli, skinTones: nil), + "🥜": EmojiWithSkinTones(baseEmoji: .peanuts, skinTones: nil), + "🥐": EmojiWithSkinTones(baseEmoji: .croissant, skinTones: nil), + "🥖": EmojiWithSkinTones(baseEmoji: .baguetteBread, skinTones: nil), + "🥨": EmojiWithSkinTones(baseEmoji: .pretzel, skinTones: nil), + "🥯": EmojiWithSkinTones(baseEmoji: .bagel, skinTones: nil), + "🥞": EmojiWithSkinTones(baseEmoji: .pancakes, skinTones: nil), + "🥩": EmojiWithSkinTones(baseEmoji: .cutOfMeat, skinTones: nil), + "🥓": EmojiWithSkinTones(baseEmoji: .bacon, skinTones: nil), + "🥪": EmojiWithSkinTones(baseEmoji: .sandwich, skinTones: nil), + "🥙": EmojiWithSkinTones(baseEmoji: .stuffedFlatbread, skinTones: nil), + "🥚": EmojiWithSkinTones(baseEmoji: .egg, skinTones: nil), + "🥘": EmojiWithSkinTones(baseEmoji: .shallowPanOfFood, skinTones: nil), + "🥣": EmojiWithSkinTones(baseEmoji: .bowlWithSpoon, skinTones: nil), + "🥗": EmojiWithSkinTones(baseEmoji: .greenSalad, skinTones: nil), + "🥫": EmojiWithSkinTones(baseEmoji: .cannedFood, skinTones: nil), + "🥮": EmojiWithSkinTones(baseEmoji: .moonCake, skinTones: nil), + "🥟": EmojiWithSkinTones(baseEmoji: .dumpling, skinTones: nil), + "🥠": EmojiWithSkinTones(baseEmoji: .fortuneCookie, skinTones: nil), + "🥡": EmojiWithSkinTones(baseEmoji: .takeoutBox, skinTones: nil), + "🥧": EmojiWithSkinTones(baseEmoji: .pie, skinTones: nil), + "🥛": EmojiWithSkinTones(baseEmoji: .glassOfMilk, skinTones: nil), + "🥂": EmojiWithSkinTones(baseEmoji: .clinkingGlasses, skinTones: nil), + "🥃": EmojiWithSkinTones(baseEmoji: .tumblerGlass, skinTones: nil), + "🥤": EmojiWithSkinTones(baseEmoji: .cupWithStraw, skinTones: nil), + "🥢": EmojiWithSkinTones(baseEmoji: .chopsticks, skinTones: nil), + "🥄": EmojiWithSkinTones(baseEmoji: .spoon, skinTones: nil), + "🥇": EmojiWithSkinTones(baseEmoji: .firstPlaceMedal, skinTones: nil), + "🥈": EmojiWithSkinTones(baseEmoji: .secondPlaceMedal, skinTones: nil), + "🥉": EmojiWithSkinTones(baseEmoji: .thirdPlaceMedal, skinTones: nil), + "🥎": EmojiWithSkinTones(baseEmoji: .softball, skinTones: nil), + "🥏": EmojiWithSkinTones(baseEmoji: .flyingDisc, skinTones: nil), + "🥍": EmojiWithSkinTones(baseEmoji: .lacrosse, skinTones: nil), + "🥊": EmojiWithSkinTones(baseEmoji: .boxingGlove, skinTones: nil), + "🥋": EmojiWithSkinTones(baseEmoji: .martialArtsUniform, skinTones: nil), + "🥅": EmojiWithSkinTones(baseEmoji: .goalNet, skinTones: nil), + "🤿": EmojiWithSkinTones(baseEmoji: .divingMask, skinTones: nil), + "🥌": EmojiWithSkinTones(baseEmoji: .curlingStone, skinTones: nil), + "🥁": EmojiWithSkinTones(baseEmoji: .drumWithDrumsticks, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1294(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🥸": EmojiWithSkinTones(baseEmoji: .disguisedFace, skinTones: nil), + "🧐": EmojiWithSkinTones(baseEmoji: .faceWithMonocle, skinTones: nil), + "🥺": EmojiWithSkinTones(baseEmoji: .pleadingFace, skinTones: nil), + "🥹": EmojiWithSkinTones(baseEmoji: .faceHoldingBackTears, skinTones: nil), + "🦾": EmojiWithSkinTones(baseEmoji: .mechanicalArm, skinTones: nil), + "🦿": EmojiWithSkinTones(baseEmoji: .mechanicalLeg, skinTones: nil), + "🦵": EmojiWithSkinTones(baseEmoji: .leg, skinTones: nil), + "🦶": EmojiWithSkinTones(baseEmoji: .foot, skinTones: nil), + "🦻": EmojiWithSkinTones(baseEmoji: .earWithHearingAid, skinTones: nil), + "🦷": EmojiWithSkinTones(baseEmoji: .tooth, skinTones: nil), + "🦴": EmojiWithSkinTones(baseEmoji: .bone, skinTones: nil), + "🧒": EmojiWithSkinTones(baseEmoji: .child, skinTones: nil), + "🧑": EmojiWithSkinTones(baseEmoji: .adult, skinTones: nil), + "🧔": EmojiWithSkinTones(baseEmoji: .beardedPerson, skinTones: nil), + "🧓": EmojiWithSkinTones(baseEmoji: .olderAdult, skinTones: nil), + "🧏": EmojiWithSkinTones(baseEmoji: .deafPerson, skinTones: nil), + "🧕": EmojiWithSkinTones(baseEmoji: .personWithHeadscarf, skinTones: nil), + "🦸": EmojiWithSkinTones(baseEmoji: .superhero, skinTones: nil), + "🦹": EmojiWithSkinTones(baseEmoji: .supervillain, skinTones: nil), + "🧙": EmojiWithSkinTones(baseEmoji: .mage, skinTones: nil), + "🧚": EmojiWithSkinTones(baseEmoji: .fairy, skinTones: nil), + "🧛": EmojiWithSkinTones(baseEmoji: .vampire, skinTones: nil), + "🧌": EmojiWithSkinTones(baseEmoji: .troll, skinTones: nil), + "🧍": EmojiWithSkinTones(baseEmoji: .standingPerson, skinTones: nil), + "🧎": EmojiWithSkinTones(baseEmoji: .kneelingPerson, skinTones: nil), + "🧖": EmojiWithSkinTones(baseEmoji: .personInSteamyRoom, skinTones: nil), + "🧗": EmojiWithSkinTones(baseEmoji: .personClimbing, skinTones: nil), + "🧘": EmojiWithSkinTones(baseEmoji: .personInLotusPosition, skinTones: nil), + "🦍": EmojiWithSkinTones(baseEmoji: .gorilla, skinTones: nil), + "🦧": EmojiWithSkinTones(baseEmoji: .orangutan, skinTones: nil), + "🦮": EmojiWithSkinTones(baseEmoji: .guideDog, skinTones: nil), + "🦊": EmojiWithSkinTones(baseEmoji: .foxFace, skinTones: nil), + "🦝": EmojiWithSkinTones(baseEmoji: .raccoon, skinTones: nil), + "🦁": EmojiWithSkinTones(baseEmoji: .lionFace, skinTones: nil), + "🦄": EmojiWithSkinTones(baseEmoji: .unicornFace, skinTones: nil), + "🦓": EmojiWithSkinTones(baseEmoji: .zebraFace, skinTones: nil), + "🦌": EmojiWithSkinTones(baseEmoji: .deer, skinTones: nil), + "🦬": EmojiWithSkinTones(baseEmoji: .bison, skinTones: nil), + "🦙": EmojiWithSkinTones(baseEmoji: .llama, skinTones: nil), + "🦒": EmojiWithSkinTones(baseEmoji: .giraffeFace, skinTones: nil), + "🦣": EmojiWithSkinTones(baseEmoji: .mammoth, skinTones: nil), + "🦏": EmojiWithSkinTones(baseEmoji: .rhinoceros, skinTones: nil), + "🦛": EmojiWithSkinTones(baseEmoji: .hippopotamus, skinTones: nil), + "🦫": EmojiWithSkinTones(baseEmoji: .beaver, skinTones: nil), + "🦔": EmojiWithSkinTones(baseEmoji: .hedgehog, skinTones: nil), + "🦇": EmojiWithSkinTones(baseEmoji: .bat, skinTones: nil), + "🦥": EmojiWithSkinTones(baseEmoji: .sloth, skinTones: nil), + "🦦": EmojiWithSkinTones(baseEmoji: .otter, skinTones: nil), + "🦨": EmojiWithSkinTones(baseEmoji: .skunk, skinTones: nil), + "🦘": EmojiWithSkinTones(baseEmoji: .kangaroo, skinTones: nil), + "🦡": EmojiWithSkinTones(baseEmoji: .badger, skinTones: nil), + "🦃": EmojiWithSkinTones(baseEmoji: .turkey, skinTones: nil), + "🦅": EmojiWithSkinTones(baseEmoji: .eagle, skinTones: nil), + "🦆": EmojiWithSkinTones(baseEmoji: .duck, skinTones: nil), + "🦢": EmojiWithSkinTones(baseEmoji: .swan, skinTones: nil), + "🦉": EmojiWithSkinTones(baseEmoji: .owl, skinTones: nil), + "🦤": EmojiWithSkinTones(baseEmoji: .dodo, skinTones: nil), + "🦩": EmojiWithSkinTones(baseEmoji: .flamingo, skinTones: nil), + "🦚": EmojiWithSkinTones(baseEmoji: .peacock, skinTones: nil), + "🦜": EmojiWithSkinTones(baseEmoji: .parrot, skinTones: nil), + "🦎": EmojiWithSkinTones(baseEmoji: .lizard, skinTones: nil), + "🦕": EmojiWithSkinTones(baseEmoji: .sauropod, skinTones: nil), + "🦖": EmojiWithSkinTones(baseEmoji: .tRex, skinTones: nil), + "🦭": EmojiWithSkinTones(baseEmoji: .seal, skinTones: nil), + "🦈": EmojiWithSkinTones(baseEmoji: .shark, skinTones: nil), + "🦋": EmojiWithSkinTones(baseEmoji: .butterfly, skinTones: nil), + "🦗": EmojiWithSkinTones(baseEmoji: .cricket, skinTones: nil), + "🦂": EmojiWithSkinTones(baseEmoji: .scorpion, skinTones: nil), + "🦟": EmojiWithSkinTones(baseEmoji: .mosquito, skinTones: nil), + "🦠": EmojiWithSkinTones(baseEmoji: .microbe, skinTones: nil), + "🧄": EmojiWithSkinTones(baseEmoji: .garlic, skinTones: nil), + "🧅": EmojiWithSkinTones(baseEmoji: .onion, skinTones: nil), + "🧇": EmojiWithSkinTones(baseEmoji: .waffle, skinTones: nil), + "🧀": EmojiWithSkinTones(baseEmoji: .cheeseWedge, skinTones: nil), + "🧆": EmojiWithSkinTones(baseEmoji: .falafel, skinTones: nil), + "🧈": EmojiWithSkinTones(baseEmoji: .butter, skinTones: nil), + "🧂": EmojiWithSkinTones(baseEmoji: .salt, skinTones: nil), + "🦀": EmojiWithSkinTones(baseEmoji: .crab, skinTones: nil), + "🦞": EmojiWithSkinTones(baseEmoji: .lobster, skinTones: nil), + "🦐": EmojiWithSkinTones(baseEmoji: .shrimp, skinTones: nil), + "🦑": EmojiWithSkinTones(baseEmoji: .squid, skinTones: nil), + "🦪": EmojiWithSkinTones(baseEmoji: .oyster, skinTones: nil), + "🧁": EmojiWithSkinTones(baseEmoji: .cupcake, skinTones: nil), + "🧋": EmojiWithSkinTones(baseEmoji: .bubbleTea, skinTones: nil), + "🧃": EmojiWithSkinTones(baseEmoji: .beverageBox, skinTones: nil), + "🧉": EmojiWithSkinTones(baseEmoji: .mateDrink, skinTones: nil), + "🧊": EmojiWithSkinTones(baseEmoji: .iceCube, skinTones: nil), + "🦽": EmojiWithSkinTones(baseEmoji: .manualWheelchair, skinTones: nil), + "🦼": EmojiWithSkinTones(baseEmoji: .motorizedWheelchair, skinTones: nil), + "🥽": EmojiWithSkinTones(baseEmoji: .goggles, skinTones: nil), + "🥼": EmojiWithSkinTones(baseEmoji: .labCoat, skinTones: nil), + "🦺": EmojiWithSkinTones(baseEmoji: .safetyVest, skinTones: nil), + "🥻": EmojiWithSkinTones(baseEmoji: .sari, skinTones: nil), + "🥾": EmojiWithSkinTones(baseEmoji: .hikingBoot, skinTones: nil), + "🥿": EmojiWithSkinTones(baseEmoji: .womansFlatShoe, skinTones: nil), + "🦯": EmojiWithSkinTones(baseEmoji: .probingCane, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1295(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧡": EmojiWithSkinTones(baseEmoji: .orangeHeart, skinTones: nil), + "🧠": EmojiWithSkinTones(baseEmoji: .brain, skinTones: nil), + "🧜": EmojiWithSkinTones(baseEmoji: .merperson, skinTones: nil), + "🧝": EmojiWithSkinTones(baseEmoji: .elf, skinTones: nil), + "🧞": EmojiWithSkinTones(baseEmoji: .genie, skinTones: nil), + "🧟": EmojiWithSkinTones(baseEmoji: .zombie, skinTones: nil), + "🧭": EmojiWithSkinTones(baseEmoji: .compass, skinTones: nil), + "🧱": EmojiWithSkinTones(baseEmoji: .bricks, skinTones: nil), + "🧳": EmojiWithSkinTones(baseEmoji: .luggage, skinTones: nil), + "🧨": EmojiWithSkinTones(baseEmoji: .firecracker, skinTones: nil), + "🧧": EmojiWithSkinTones(baseEmoji: .redEnvelope, skinTones: nil), + "🧩": EmojiWithSkinTones(baseEmoji: .jigsaw, skinTones: nil), + "🧸": EmojiWithSkinTones(baseEmoji: .teddyBear, skinTones: nil), + "🧵": EmojiWithSkinTones(baseEmoji: .thread, skinTones: nil), + "🧶": EmojiWithSkinTones(baseEmoji: .yarn, skinTones: nil), + "🧣": EmojiWithSkinTones(baseEmoji: .scarf, skinTones: nil), + "🧤": EmojiWithSkinTones(baseEmoji: .gloves, skinTones: nil), + "🧥": EmojiWithSkinTones(baseEmoji: .coat, skinTones: nil), + "🧦": EmojiWithSkinTones(baseEmoji: .socks, skinTones: nil), + "🧢": EmojiWithSkinTones(baseEmoji: .billedCap, skinTones: nil), + "🧮": EmojiWithSkinTones(baseEmoji: .abacus, skinTones: nil), + "🧾": EmojiWithSkinTones(baseEmoji: .receipt, skinTones: nil), + "🧰": EmojiWithSkinTones(baseEmoji: .toolbox, skinTones: nil), + "🧲": EmojiWithSkinTones(baseEmoji: .magnet, skinTones: nil), + "🧪": EmojiWithSkinTones(baseEmoji: .testTube, skinTones: nil), + "🧫": EmojiWithSkinTones(baseEmoji: .petriDish, skinTones: nil), + "🧬": EmojiWithSkinTones(baseEmoji: .dna, skinTones: nil), + "🧴": EmojiWithSkinTones(baseEmoji: .lotionBottle, skinTones: nil), + "🧷": EmojiWithSkinTones(baseEmoji: .safetyPin, skinTones: nil), + "🧹": EmojiWithSkinTones(baseEmoji: .broom, skinTones: nil), + "🧺": EmojiWithSkinTones(baseEmoji: .basket, skinTones: nil), + "🧻": EmojiWithSkinTones(baseEmoji: .rollOfPaper, skinTones: nil), + "🧼": EmojiWithSkinTones(baseEmoji: .soap, skinTones: nil), + "🧽": EmojiWithSkinTones(baseEmoji: .sponge, skinTones: nil), + "🧯": EmojiWithSkinTones(baseEmoji: .fireExtinguisher, skinTones: nil), + "🧿": EmojiWithSkinTones(baseEmoji: .nazarAmulet, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1296(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🩷": EmojiWithSkinTones(baseEmoji: .pinkHeart, skinTones: nil), + "🩵": EmojiWithSkinTones(baseEmoji: .lightBlueHeart, skinTones: nil), + "🩶": EmojiWithSkinTones(baseEmoji: .greyHeart, skinTones: nil), + "🪂": EmojiWithSkinTones(baseEmoji: .parachute, skinTones: nil), + "🪐": EmojiWithSkinTones(baseEmoji: .ringedPlanet, skinTones: nil), + "🪀": EmojiWithSkinTones(baseEmoji: .yoYo, skinTones: nil), + "🪁": EmojiWithSkinTones(baseEmoji: .kite, skinTones: nil), + "🪄": EmojiWithSkinTones(baseEmoji: .magicWand, skinTones: nil), + "🪅": EmojiWithSkinTones(baseEmoji: .pinata, skinTones: nil), + "🪆": EmojiWithSkinTones(baseEmoji: .nestingDolls, skinTones: nil), + "🪡": EmojiWithSkinTones(baseEmoji: .sewingNeedle, skinTones: nil), + "🪢": EmojiWithSkinTones(baseEmoji: .knot, skinTones: nil), + "🩱": EmojiWithSkinTones(baseEmoji: .onePieceSwimsuit, skinTones: nil), + "🩲": EmojiWithSkinTones(baseEmoji: .briefs, skinTones: nil), + "🩳": EmojiWithSkinTones(baseEmoji: .shorts, skinTones: nil), + "🩴": EmojiWithSkinTones(baseEmoji: .thongSandal, skinTones: nil), + "🩰": EmojiWithSkinTones(baseEmoji: .balletShoes, skinTones: nil), + "🪖": EmojiWithSkinTones(baseEmoji: .militaryHelmet, skinTones: nil), + "🪗": EmojiWithSkinTones(baseEmoji: .accordion, skinTones: nil), + "🪕": EmojiWithSkinTones(baseEmoji: .banjo, skinTones: nil), + "🪘": EmojiWithSkinTones(baseEmoji: .longDrum, skinTones: nil), + "🪇": EmojiWithSkinTones(baseEmoji: .maracas, skinTones: nil), + "🪈": EmojiWithSkinTones(baseEmoji: .flute, skinTones: nil), + "🪔": EmojiWithSkinTones(baseEmoji: .diyaLamp, skinTones: nil), + "🪙": EmojiWithSkinTones(baseEmoji: .coin, skinTones: nil), + "🪓": EmojiWithSkinTones(baseEmoji: .axe, skinTones: nil), + "🪃": EmojiWithSkinTones(baseEmoji: .boomerang, skinTones: nil), + "🪚": EmojiWithSkinTones(baseEmoji: .carpentrySaw, skinTones: nil), + "🪛": EmojiWithSkinTones(baseEmoji: .screwdriver, skinTones: nil), + "🪝": EmojiWithSkinTones(baseEmoji: .hook, skinTones: nil), + "🪜": EmojiWithSkinTones(baseEmoji: .ladder, skinTones: nil), + "🩸": EmojiWithSkinTones(baseEmoji: .dropOfBlood, skinTones: nil), + "🩹": EmojiWithSkinTones(baseEmoji: .adhesiveBandage, skinTones: nil), + "🩼": EmojiWithSkinTones(baseEmoji: .crutch, skinTones: nil), + "🩺": EmojiWithSkinTones(baseEmoji: .stethoscope, skinTones: nil), + "🩻": EmojiWithSkinTones(baseEmoji: .xRay, skinTones: nil), + "🪞": EmojiWithSkinTones(baseEmoji: .mirror, skinTones: nil), + "🪟": EmojiWithSkinTones(baseEmoji: .window, skinTones: nil), + "🪑": EmojiWithSkinTones(baseEmoji: .chair, skinTones: nil), + "🪠": EmojiWithSkinTones(baseEmoji: .plunger, skinTones: nil), + "🪒": EmojiWithSkinTones(baseEmoji: .razor, skinTones: nil), + "🪣": EmojiWithSkinTones(baseEmoji: .bucket, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1297(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🫠": EmojiWithSkinTones(baseEmoji: .meltingFace, skinTones: nil), + "🫢": EmojiWithSkinTones(baseEmoji: .faceWithOpenEyesAndHandOverMouth, skinTones: nil), + "🫣": EmojiWithSkinTones(baseEmoji: .faceWithPeekingEye, skinTones: nil), + "🫡": EmojiWithSkinTones(baseEmoji: .salutingFace, skinTones: nil), + "🫥": EmojiWithSkinTones(baseEmoji: .dottedLineFace, skinTones: nil), + "🫨": EmojiWithSkinTones(baseEmoji: .shakingFace, skinTones: nil), + "🫤": EmojiWithSkinTones(baseEmoji: .faceWithDiagonalMouth, skinTones: nil), + "🫱": EmojiWithSkinTones(baseEmoji: .rightwardsHand, skinTones: nil), + "🫲": EmojiWithSkinTones(baseEmoji: .leftwardsHand, skinTones: nil), + "🫳": EmojiWithSkinTones(baseEmoji: .palmDownHand, skinTones: nil), + "🫴": EmojiWithSkinTones(baseEmoji: .palmUpHand, skinTones: nil), + "🫷": EmojiWithSkinTones(baseEmoji: .leftwardsPushingHand, skinTones: nil), + "🫸": EmojiWithSkinTones(baseEmoji: .rightwardsPushingHand, skinTones: nil), + "🫰": EmojiWithSkinTones(baseEmoji: .handWithIndexFingerAndThumbCrossed, skinTones: nil), + "🫵": EmojiWithSkinTones(baseEmoji: .indexPointingAtTheViewer, skinTones: nil), + "🫶": EmojiWithSkinTones(baseEmoji: .heartHands, skinTones: nil), + "🫀": EmojiWithSkinTones(baseEmoji: .anatomicalHeart, skinTones: nil), + "🫁": EmojiWithSkinTones(baseEmoji: .lungs, skinTones: nil), + "🫦": EmojiWithSkinTones(baseEmoji: .bitingLip, skinTones: nil), + "🫅": EmojiWithSkinTones(baseEmoji: .personWithCrown, skinTones: nil), + "🫃": EmojiWithSkinTones(baseEmoji: .pregnantMan, skinTones: nil), + "🫄": EmojiWithSkinTones(baseEmoji: .pregnantPerson, skinTones: nil), + "🫂": EmojiWithSkinTones(baseEmoji: .peopleHugging, skinTones: nil), + "🫎": EmojiWithSkinTones(baseEmoji: .moose, skinTones: nil), + "🫏": EmojiWithSkinTones(baseEmoji: .donkey, skinTones: nil), + "🪶": EmojiWithSkinTones(baseEmoji: .feather, skinTones: nil), + "🪽": EmojiWithSkinTones(baseEmoji: .wing, skinTones: nil), + "🪿": EmojiWithSkinTones(baseEmoji: .goose, skinTones: nil), + "🪸": EmojiWithSkinTones(baseEmoji: .coral, skinTones: nil), + "🪼": EmojiWithSkinTones(baseEmoji: .jellyfish, skinTones: nil), + "🪲": EmojiWithSkinTones(baseEmoji: .beetle, skinTones: nil), + "🪳": EmojiWithSkinTones(baseEmoji: .cockroach, skinTones: nil), + "🪰": EmojiWithSkinTones(baseEmoji: .fly, skinTones: nil), + "🪱": EmojiWithSkinTones(baseEmoji: .worm, skinTones: nil), + "🪷": EmojiWithSkinTones(baseEmoji: .lotus, skinTones: nil), + "🪻": EmojiWithSkinTones(baseEmoji: .hyacinth, skinTones: nil), + "🪴": EmojiWithSkinTones(baseEmoji: .pottedPlant, skinTones: nil), + "🪹": EmojiWithSkinTones(baseEmoji: .emptyNest, skinTones: nil), + "🪺": EmojiWithSkinTones(baseEmoji: .nestWithEggs, skinTones: nil), + "🫐": EmojiWithSkinTones(baseEmoji: .blueberries, skinTones: nil), + "🫒": EmojiWithSkinTones(baseEmoji: .olive, skinTones: nil), + "🫑": EmojiWithSkinTones(baseEmoji: .bellPepper, skinTones: nil), + "🫘": EmojiWithSkinTones(baseEmoji: .beans, skinTones: nil), + "🫚": EmojiWithSkinTones(baseEmoji: .gingerRoot, skinTones: nil), + "🫛": EmojiWithSkinTones(baseEmoji: .peaPod, skinTones: nil), + "🫓": EmojiWithSkinTones(baseEmoji: .flatbread, skinTones: nil), + "🫔": EmojiWithSkinTones(baseEmoji: .tamale, skinTones: nil), + "🫕": EmojiWithSkinTones(baseEmoji: .fondue, skinTones: nil), + "🫖": EmojiWithSkinTones(baseEmoji: .teapot, skinTones: nil), + "🫗": EmojiWithSkinTones(baseEmoji: .pouringLiquid, skinTones: nil), + "🫙": EmojiWithSkinTones(baseEmoji: .jar, skinTones: nil), + "🪨": EmojiWithSkinTones(baseEmoji: .rock, skinTones: nil), + "🪵": EmojiWithSkinTones(baseEmoji: .wood, skinTones: nil), + "🪩": EmojiWithSkinTones(baseEmoji: .mirrorBall, skinTones: nil), + "🪭": EmojiWithSkinTones(baseEmoji: .foldingHandFan, skinTones: nil), + "🪮": EmojiWithSkinTones(baseEmoji: .hairPick, skinTones: nil), + "🪫": EmojiWithSkinTones(baseEmoji: .lowBattery, skinTones: nil), + "🪤": EmojiWithSkinTones(baseEmoji: .mouseTrap, skinTones: nil), + "🫧": EmojiWithSkinTones(baseEmoji: .bubbles, skinTones: nil), + "🪥": EmojiWithSkinTones(baseEmoji: .toothbrush, skinTones: nil), + "🪦": EmojiWithSkinTones(baseEmoji: .headstone, skinTones: nil), + "🪬": EmojiWithSkinTones(baseEmoji: .hamsa, skinTones: nil), + "🪧": EmojiWithSkinTones(baseEmoji: .placard, skinTones: nil), + "🪪": EmojiWithSkinTones(baseEmoji: .identificationCard, skinTones: nil), + "🪯": EmojiWithSkinTones(baseEmoji: .khanda, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1377(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "☝🏻": EmojiWithSkinTones(baseEmoji: .pointUp, skinTones: [.light]), + "☝🏼": EmojiWithSkinTones(baseEmoji: .pointUp, skinTones: [.mediumLight]), + "☝🏽": EmojiWithSkinTones(baseEmoji: .pointUp, skinTones: [.medium]), + "☝🏾": EmojiWithSkinTones(baseEmoji: .pointUp, skinTones: [.mediumDark]), + "☝🏿": EmojiWithSkinTones(baseEmoji: .pointUp, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1379(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "✋🏻": EmojiWithSkinTones(baseEmoji: .hand, skinTones: [.light]), + "✌🏻": EmojiWithSkinTones(baseEmoji: .v, skinTones: [.light]), + "✊🏻": EmojiWithSkinTones(baseEmoji: .fist, skinTones: [.light]), + "✍🏻": EmojiWithSkinTones(baseEmoji: .writingHand, skinTones: [.light]), + "⛹🏻": EmojiWithSkinTones(baseEmoji: .personWithBall, skinTones: [.light]), + "✋🏼": EmojiWithSkinTones(baseEmoji: .hand, skinTones: [.mediumLight]), + "✌🏼": EmojiWithSkinTones(baseEmoji: .v, skinTones: [.mediumLight]), + "✊🏼": EmojiWithSkinTones(baseEmoji: .fist, skinTones: [.mediumLight]), + "✍🏼": EmojiWithSkinTones(baseEmoji: .writingHand, skinTones: [.mediumLight]), + "⛹🏼": EmojiWithSkinTones(baseEmoji: .personWithBall, skinTones: [.mediumLight]), + "✋🏽": EmojiWithSkinTones(baseEmoji: .hand, skinTones: [.medium]), + "✌🏽": EmojiWithSkinTones(baseEmoji: .v, skinTones: [.medium]), + "✊🏽": EmojiWithSkinTones(baseEmoji: .fist, skinTones: [.medium]), + "✍🏽": EmojiWithSkinTones(baseEmoji: .writingHand, skinTones: [.medium]), + "⛹🏽": EmojiWithSkinTones(baseEmoji: .personWithBall, skinTones: [.medium]), + "✋🏾": EmojiWithSkinTones(baseEmoji: .hand, skinTones: [.mediumDark]), + "✌🏾": EmojiWithSkinTones(baseEmoji: .v, skinTones: [.mediumDark]), + "✊🏾": EmojiWithSkinTones(baseEmoji: .fist, skinTones: [.mediumDark]), + "✍🏾": EmojiWithSkinTones(baseEmoji: .writingHand, skinTones: [.mediumDark]), + "⛹🏾": EmojiWithSkinTones(baseEmoji: .personWithBall, skinTones: [.mediumDark]), + "✋🏿": EmojiWithSkinTones(baseEmoji: .hand, skinTones: [.dark]), + "✌🏿": EmojiWithSkinTones(baseEmoji: .v, skinTones: [.dark]), + "✊🏿": EmojiWithSkinTones(baseEmoji: .fist, skinTones: [.dark]), + "✍🏿": EmojiWithSkinTones(baseEmoji: .writingHand, skinTones: [.dark]), + "⛹🏿": EmojiWithSkinTones(baseEmoji: .personWithBall, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1472(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🐈‍⬛": EmojiWithSkinTones(baseEmoji: .blackCat, skinTones: nil), + "🐦‍⬛": EmojiWithSkinTones(baseEmoji: .blackBird, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1580(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "⛹️‍♂️": EmojiWithSkinTones(baseEmoji: .manBouncingBall, skinTones: nil), + "⛹️‍♀️": EmojiWithSkinTones(baseEmoji: .womanBouncingBall, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1923(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🅰️": EmojiWithSkinTones(baseEmoji: .a, skinTones: nil), + "🅱️": EmojiWithSkinTones(baseEmoji: .b, skinTones: nil), + "🅾️": EmojiWithSkinTones(baseEmoji: .o2, skinTones: nil), + "🅿️": EmojiWithSkinTones(baseEmoji: .parking, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1925(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🈂️": EmojiWithSkinTones(baseEmoji: .sa, skinTones: nil), + "🈷️": EmojiWithSkinTones(baseEmoji: .u6708, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1928(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🌶️": EmojiWithSkinTones(baseEmoji: .hotPepper, skinTones: nil), + "🌡️": EmojiWithSkinTones(baseEmoji: .thermometer, skinTones: nil), + "🌤️": EmojiWithSkinTones(baseEmoji: .mostlySunny, skinTones: nil), + "🌥️": EmojiWithSkinTones(baseEmoji: .barelySunny, skinTones: nil), + "🌦️": EmojiWithSkinTones(baseEmoji: .partlySunnyRain, skinTones: nil), + "🌧️": EmojiWithSkinTones(baseEmoji: .rainCloud, skinTones: nil), + "🌨️": EmojiWithSkinTones(baseEmoji: .snowCloud, skinTones: nil), + "🌩️": EmojiWithSkinTones(baseEmoji: .lightning, skinTones: nil), + "🌪️": EmojiWithSkinTones(baseEmoji: .tornado, skinTones: nil), + "🌫️": EmojiWithSkinTones(baseEmoji: .fog, skinTones: nil), + "🌬️": EmojiWithSkinTones(baseEmoji: .windBlowingFace, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1929(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏌️": EmojiWithSkinTones(baseEmoji: .golfer, skinTones: nil), + "🏋️": EmojiWithSkinTones(baseEmoji: .weightLifter, skinTones: nil), + "🍽️": EmojiWithSkinTones(baseEmoji: .knifeForkPlate, skinTones: nil), + "🏔️": EmojiWithSkinTones(baseEmoji: .snowCappedMountain, skinTones: nil), + "🏕️": EmojiWithSkinTones(baseEmoji: .camping, skinTones: nil), + "🏖️": EmojiWithSkinTones(baseEmoji: .beachWithUmbrella, skinTones: nil), + "🏗️": EmojiWithSkinTones(baseEmoji: .buildingConstruction, skinTones: nil), + "🏘️": EmojiWithSkinTones(baseEmoji: .houseBuildings, skinTones: nil), + "🏎️": EmojiWithSkinTones(baseEmoji: .racingCar, skinTones: nil), + "🏍️": EmojiWithSkinTones(baseEmoji: .racingMotorcycle, skinTones: nil), + "🎗️": EmojiWithSkinTones(baseEmoji: .reminderRibbon, skinTones: nil), + "🎟️": EmojiWithSkinTones(baseEmoji: .admissionTickets, skinTones: nil), + "🎖️": EmojiWithSkinTones(baseEmoji: .medal, skinTones: nil), + "🎙️": EmojiWithSkinTones(baseEmoji: .studioMicrophone, skinTones: nil), + "🎚️": EmojiWithSkinTones(baseEmoji: .levelSlider, skinTones: nil), + "🎛️": EmojiWithSkinTones(baseEmoji: .controlKnobs, skinTones: nil), + "🎞️": EmojiWithSkinTones(baseEmoji: .filmFrames, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1930(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏵️": EmojiWithSkinTones(baseEmoji: .rosette, skinTones: nil), + "🏜️": EmojiWithSkinTones(baseEmoji: .desert, skinTones: nil), + "🏝️": EmojiWithSkinTones(baseEmoji: .desertIsland, skinTones: nil), + "🏞️": EmojiWithSkinTones(baseEmoji: .nationalPark, skinTones: nil), + "🏟️": EmojiWithSkinTones(baseEmoji: .stadium, skinTones: nil), + "🏛️": EmojiWithSkinTones(baseEmoji: .classicalBuilding, skinTones: nil), + "🏚️": EmojiWithSkinTones(baseEmoji: .derelictHouseBuilding, skinTones: nil), + "🏙️": EmojiWithSkinTones(baseEmoji: .cityscape, skinTones: nil), + "🏷️": EmojiWithSkinTones(baseEmoji: .label, skinTones: nil), + "🏳️": EmojiWithSkinTones(baseEmoji: .wavingWhiteFlag, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1931(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👁️": EmojiWithSkinTones(baseEmoji: .eye, skinTones: nil), + "🐿️": EmojiWithSkinTones(baseEmoji: .chipmunk, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1932(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "📽️": EmojiWithSkinTones(baseEmoji: .filmProjector, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1933(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🕊️": EmojiWithSkinTones(baseEmoji: .doveOfPeace, skinTones: nil), + "🕉️": EmojiWithSkinTones(baseEmoji: .omSymbol, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1934(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🕳️": EmojiWithSkinTones(baseEmoji: .hole, skinTones: nil), + "🖐️": EmojiWithSkinTones(baseEmoji: .raisedHandWithFingersSplayed, skinTones: nil), + "🕵️": EmojiWithSkinTones(baseEmoji: .sleuthOrSpy, skinTones: nil), + "🕴️": EmojiWithSkinTones(baseEmoji: .manInBusinessSuitLevitating, skinTones: nil), + "🕷️": EmojiWithSkinTones(baseEmoji: .spider, skinTones: nil), + "🕸️": EmojiWithSkinTones(baseEmoji: .spiderWeb, skinTones: nil), + "🕰️": EmojiWithSkinTones(baseEmoji: .mantelpieceClock, skinTones: nil), + "🕹️": EmojiWithSkinTones(baseEmoji: .joystick, skinTones: nil), + "🖼️": EmojiWithSkinTones(baseEmoji: .frameWithPicture, skinTones: nil), + "🕶️": EmojiWithSkinTones(baseEmoji: .darkSunglasses, skinTones: nil), + "🖥️": EmojiWithSkinTones(baseEmoji: .desktopComputer, skinTones: nil), + "🖨️": EmojiWithSkinTones(baseEmoji: .printer, skinTones: nil), + "🖱️": EmojiWithSkinTones(baseEmoji: .threeButtonMouse, skinTones: nil), + "🖲️": EmojiWithSkinTones(baseEmoji: .trackball, skinTones: nil), + "🕯️": EmojiWithSkinTones(baseEmoji: .candle, skinTones: nil), + "🖋️": EmojiWithSkinTones(baseEmoji: .lowerLeftFountainPen, skinTones: nil), + "🖊️": EmojiWithSkinTones(baseEmoji: .lowerLeftBallpointPen, skinTones: nil), + "🖌️": EmojiWithSkinTones(baseEmoji: .lowerLeftPaintbrush, skinTones: nil), + "🖍️": EmojiWithSkinTones(baseEmoji: .lowerLeftCrayon, skinTones: nil), + "🗂️": EmojiWithSkinTones(baseEmoji: .cardIndexDividers, skinTones: nil), + "🖇️": EmojiWithSkinTones(baseEmoji: .linkedPaperclips, skinTones: nil), + "🗃️": EmojiWithSkinTones(baseEmoji: .cardFileBox, skinTones: nil), + "🗄️": EmojiWithSkinTones(baseEmoji: .fileCabinet, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1935(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🗨️": EmojiWithSkinTones(baseEmoji: .leftSpeechBubble, skinTones: nil), + "🗯️": EmojiWithSkinTones(baseEmoji: .rightAngerBubble, skinTones: nil), + "🗣️": EmojiWithSkinTones(baseEmoji: .speakingHeadInSilhouette, skinTones: nil), + "🗺️": EmojiWithSkinTones(baseEmoji: .worldMap, skinTones: nil), + "🗞️": EmojiWithSkinTones(baseEmoji: .rolledUpNewspaper, skinTones: nil), + "🗳️": EmojiWithSkinTones(baseEmoji: .ballotBoxWithBallot, skinTones: nil), + "🗒️": EmojiWithSkinTones(baseEmoji: .spiralNotePad, skinTones: nil), + "🗓️": EmojiWithSkinTones(baseEmoji: .spiralCalendarPad, skinTones: nil), + "🗑️": EmojiWithSkinTones(baseEmoji: .wastebasket, skinTones: nil), + "🗝️": EmojiWithSkinTones(baseEmoji: .oldKey, skinTones: nil), + "🗡️": EmojiWithSkinTones(baseEmoji: .daggerKnife, skinTones: nil), + "🗜️": EmojiWithSkinTones(baseEmoji: .compression, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom1937(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🛣️": EmojiWithSkinTones(baseEmoji: .motorway, skinTones: nil), + "🛤️": EmojiWithSkinTones(baseEmoji: .railwayTrack, skinTones: nil), + "🛢️": EmojiWithSkinTones(baseEmoji: .oilDrum, skinTones: nil), + "🛳️": EmojiWithSkinTones(baseEmoji: .passengerShip, skinTones: nil), + "🛥️": EmojiWithSkinTones(baseEmoji: .motorBoat, skinTones: nil), + "🛩️": EmojiWithSkinTones(baseEmoji: .smallAirplane, skinTones: nil), + "🛰️": EmojiWithSkinTones(baseEmoji: .satellite, skinTones: nil), + "🛎️": EmojiWithSkinTones(baseEmoji: .bellhopBell, skinTones: nil), + "🛍️": EmojiWithSkinTones(baseEmoji: .shoppingBags, skinTones: nil), + "🛠️": EmojiWithSkinTones(baseEmoji: .hammerAndWrench, skinTones: nil), + "🛡️": EmojiWithSkinTones(baseEmoji: .shield, skinTones: nil), + "🛏️": EmojiWithSkinTones(baseEmoji: .bed, skinTones: nil), + "🛋️": EmojiWithSkinTones(baseEmoji: .couchAndLamp, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2109(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏃‍♂️": EmojiWithSkinTones(baseEmoji: .manRunning, skinTones: nil), + "🏃‍♀️": EmojiWithSkinTones(baseEmoji: .womanRunning, skinTones: nil), + "🏄‍♂️": EmojiWithSkinTones(baseEmoji: .manSurfing, skinTones: nil), + "🏄‍♀️": EmojiWithSkinTones(baseEmoji: .womanSurfing, skinTones: nil), + "🏊‍♂️": EmojiWithSkinTones(baseEmoji: .manSwimming, skinTones: nil), + "🏊‍♀️": EmojiWithSkinTones(baseEmoji: .womanSwimming, skinTones: nil), + "🏴‍☠️": EmojiWithSkinTones(baseEmoji: .pirateFlag, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2111(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👱‍♀️": EmojiWithSkinTones(baseEmoji: .blondHairedWoman, skinTones: nil), + "👱‍♂️": EmojiWithSkinTones(baseEmoji: .blondHairedMan, skinTones: nil), + "💁‍♂️": EmojiWithSkinTones(baseEmoji: .manTippingHand, skinTones: nil), + "💁‍♀️": EmojiWithSkinTones(baseEmoji: .womanTippingHand, skinTones: nil), + "👮‍♂️": EmojiWithSkinTones(baseEmoji: .malePoliceOfficer, skinTones: nil), + "👮‍♀️": EmojiWithSkinTones(baseEmoji: .femalePoliceOfficer, skinTones: nil), + "💂‍♂️": EmojiWithSkinTones(baseEmoji: .maleGuard, skinTones: nil), + "💂‍♀️": EmojiWithSkinTones(baseEmoji: .femaleGuard, skinTones: nil), + "👷‍♂️": EmojiWithSkinTones(baseEmoji: .maleConstructionWorker, skinTones: nil), + "👷‍♀️": EmojiWithSkinTones(baseEmoji: .femaleConstructionWorker, skinTones: nil), + "👳‍♂️": EmojiWithSkinTones(baseEmoji: .manWearingTurban, skinTones: nil), + "👳‍♀️": EmojiWithSkinTones(baseEmoji: .womanWearingTurban, skinTones: nil), + "👰‍♂️": EmojiWithSkinTones(baseEmoji: .manWithVeil, skinTones: nil), + "👰‍♀️": EmojiWithSkinTones(baseEmoji: .womanWithVeil, skinTones: nil), + "💆‍♂️": EmojiWithSkinTones(baseEmoji: .manGettingMassage, skinTones: nil), + "💆‍♀️": EmojiWithSkinTones(baseEmoji: .womanGettingMassage, skinTones: nil), + "💇‍♂️": EmojiWithSkinTones(baseEmoji: .manGettingHaircut, skinTones: nil), + "💇‍♀️": EmojiWithSkinTones(baseEmoji: .womanGettingHaircut, skinTones: nil), + "👯‍♂️": EmojiWithSkinTones(baseEmoji: .menWithBunnyEarsPartying, skinTones: nil), + "👯‍♀️": EmojiWithSkinTones(baseEmoji: .womenWithBunnyEarsPartying, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2112(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨‍⚕️": EmojiWithSkinTones(baseEmoji: .maleDoctor, skinTones: nil), + "👩‍⚕️": EmojiWithSkinTones(baseEmoji: .femaleDoctor, skinTones: nil), + "👨‍⚖️": EmojiWithSkinTones(baseEmoji: .maleJudge, skinTones: nil), + "👩‍⚖️": EmojiWithSkinTones(baseEmoji: .femaleJudge, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2113(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: nil), + "👩‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: nil), + "🐻‍❄️": EmojiWithSkinTones(baseEmoji: .polarBear, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2116(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "❤️‍🔥": EmojiWithSkinTones(baseEmoji: .heartOnFire, skinTones: nil), + "🙍‍♂️": EmojiWithSkinTones(baseEmoji: .manFrowning, skinTones: nil), + "🙍‍♀️": EmojiWithSkinTones(baseEmoji: .womanFrowning, skinTones: nil), + "🙎‍♂️": EmojiWithSkinTones(baseEmoji: .manPouting, skinTones: nil), + "🙎‍♀️": EmojiWithSkinTones(baseEmoji: .womanPouting, skinTones: nil), + "🙅‍♂️": EmojiWithSkinTones(baseEmoji: .manGesturingNo, skinTones: nil), + "🙅‍♀️": EmojiWithSkinTones(baseEmoji: .womanGesturingNo, skinTones: nil), + "🙆‍♂️": EmojiWithSkinTones(baseEmoji: .manGesturingOk, skinTones: nil), + "🙆‍♀️": EmojiWithSkinTones(baseEmoji: .womanGesturingOk, skinTones: nil), + "🙋‍♂️": EmojiWithSkinTones(baseEmoji: .manRaisingHand, skinTones: nil), + "🙋‍♀️": EmojiWithSkinTones(baseEmoji: .womanRaisingHand, skinTones: nil), + "🙇‍♂️": EmojiWithSkinTones(baseEmoji: .manBowing, skinTones: nil), + "🙇‍♀️": EmojiWithSkinTones(baseEmoji: .womanBowing, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2117(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🚶‍♂️": EmojiWithSkinTones(baseEmoji: .manWalking, skinTones: nil), + "🚶‍♀️": EmojiWithSkinTones(baseEmoji: .womanWalking, skinTones: nil), + "🚣‍♂️": EmojiWithSkinTones(baseEmoji: .manRowingBoat, skinTones: nil), + "🚣‍♀️": EmojiWithSkinTones(baseEmoji: .womanRowingBoat, skinTones: nil), + "🚴‍♂️": EmojiWithSkinTones(baseEmoji: .manBiking, skinTones: nil), + "🚴‍♀️": EmojiWithSkinTones(baseEmoji: .womanBiking, skinTones: nil), + "🚵‍♂️": EmojiWithSkinTones(baseEmoji: .manMountainBiking, skinTones: nil), + "🚵‍♀️": EmojiWithSkinTones(baseEmoji: .womanMountainBiking, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2123(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🤦‍♂️": EmojiWithSkinTones(baseEmoji: .manFacepalming, skinTones: nil), + "🤦‍♀️": EmojiWithSkinTones(baseEmoji: .womanFacepalming, skinTones: nil), + "🤷‍♂️": EmojiWithSkinTones(baseEmoji: .manShrugging, skinTones: nil), + "🤷‍♀️": EmojiWithSkinTones(baseEmoji: .womanShrugging, skinTones: nil), + "🤵‍♂️": EmojiWithSkinTones(baseEmoji: .manInTuxedo, skinTones: nil), + "🤵‍♀️": EmojiWithSkinTones(baseEmoji: .womanInTuxedo, skinTones: nil), + "🤸‍♂️": EmojiWithSkinTones(baseEmoji: .manCartwheeling, skinTones: nil), + "🤸‍♀️": EmojiWithSkinTones(baseEmoji: .womanCartwheeling, skinTones: nil), + "🤼‍♂️": EmojiWithSkinTones(baseEmoji: .manWrestling, skinTones: nil), + "🤼‍♀️": EmojiWithSkinTones(baseEmoji: .womanWrestling, skinTones: nil), + "🤽‍♂️": EmojiWithSkinTones(baseEmoji: .manPlayingWaterPolo, skinTones: nil), + "🤽‍♀️": EmojiWithSkinTones(baseEmoji: .womanPlayingWaterPolo, skinTones: nil), + "🤾‍♂️": EmojiWithSkinTones(baseEmoji: .manPlayingHandball, skinTones: nil), + "🤾‍♀️": EmojiWithSkinTones(baseEmoji: .womanPlayingHandball, skinTones: nil), + "🤹‍♂️": EmojiWithSkinTones(baseEmoji: .manJuggling, skinTones: nil), + "🤹‍♀️": EmojiWithSkinTones(baseEmoji: .womanJuggling, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2125(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧔‍♂️": EmojiWithSkinTones(baseEmoji: .manWithBeard, skinTones: nil), + "🧔‍♀️": EmojiWithSkinTones(baseEmoji: .womanWithBeard, skinTones: nil), + "🧏‍♂️": EmojiWithSkinTones(baseEmoji: .deafMan, skinTones: nil), + "🧏‍♀️": EmojiWithSkinTones(baseEmoji: .deafWoman, skinTones: nil), + "🦸‍♂️": EmojiWithSkinTones(baseEmoji: .maleSuperhero, skinTones: nil), + "🦸‍♀️": EmojiWithSkinTones(baseEmoji: .femaleSuperhero, skinTones: nil), + "🦹‍♂️": EmojiWithSkinTones(baseEmoji: .maleSupervillain, skinTones: nil), + "🦹‍♀️": EmojiWithSkinTones(baseEmoji: .femaleSupervillain, skinTones: nil), + "🧙‍♂️": EmojiWithSkinTones(baseEmoji: .maleMage, skinTones: nil), + "🧙‍♀️": EmojiWithSkinTones(baseEmoji: .femaleMage, skinTones: nil), + "🧚‍♂️": EmojiWithSkinTones(baseEmoji: .maleFairy, skinTones: nil), + "🧚‍♀️": EmojiWithSkinTones(baseEmoji: .femaleFairy, skinTones: nil), + "🧛‍♂️": EmojiWithSkinTones(baseEmoji: .maleVampire, skinTones: nil), + "🧛‍♀️": EmojiWithSkinTones(baseEmoji: .femaleVampire, skinTones: nil), + "🧜‍♂️": EmojiWithSkinTones(baseEmoji: .merman, skinTones: nil), + "🧜‍♀️": EmojiWithSkinTones(baseEmoji: .mermaid, skinTones: nil), + "🧝‍♂️": EmojiWithSkinTones(baseEmoji: .maleElf, skinTones: nil), + "🧝‍♀️": EmojiWithSkinTones(baseEmoji: .femaleElf, skinTones: nil), + "🧞‍♂️": EmojiWithSkinTones(baseEmoji: .maleGenie, skinTones: nil), + "🧞‍♀️": EmojiWithSkinTones(baseEmoji: .femaleGenie, skinTones: nil), + "🧟‍♂️": EmojiWithSkinTones(baseEmoji: .maleZombie, skinTones: nil), + "🧟‍♀️": EmojiWithSkinTones(baseEmoji: .femaleZombie, skinTones: nil), + "🧍‍♂️": EmojiWithSkinTones(baseEmoji: .manStanding, skinTones: nil), + "🧍‍♀️": EmojiWithSkinTones(baseEmoji: .womanStanding, skinTones: nil), + "🧎‍♂️": EmojiWithSkinTones(baseEmoji: .manKneeling, skinTones: nil), + "🧎‍♀️": EmojiWithSkinTones(baseEmoji: .womanKneeling, skinTones: nil), + "🧖‍♂️": EmojiWithSkinTones(baseEmoji: .manInSteamyRoom, skinTones: nil), + "🧖‍♀️": EmojiWithSkinTones(baseEmoji: .womanInSteamyRoom, skinTones: nil), + "🧗‍♂️": EmojiWithSkinTones(baseEmoji: .manClimbing, skinTones: nil), + "🧗‍♀️": EmojiWithSkinTones(baseEmoji: .womanClimbing, skinTones: nil), + "🧘‍♂️": EmojiWithSkinTones(baseEmoji: .manInLotusPosition, skinTones: nil), + "🧘‍♀️": EmojiWithSkinTones(baseEmoji: .womanInLotusPosition, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2126(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍⚕️": EmojiWithSkinTones(baseEmoji: .healthWorker, skinTones: nil), + "🧑‍⚖️": EmojiWithSkinTones(baseEmoji: .judge, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2127(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍✈️": EmojiWithSkinTones(baseEmoji: .pilot, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2129(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "❤️‍🩹": EmojiWithSkinTones(baseEmoji: .mendingHeart, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2210(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "⛹🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manBouncingBall, skinTones: [.light]), + "⛹🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanBouncingBall, skinTones: [.light]), + "⛹🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manBouncingBall, skinTones: [.mediumLight]), + "⛹🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanBouncingBall, skinTones: [.mediumLight]), + "⛹🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manBouncingBall, skinTones: [.medium]), + "⛹🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanBouncingBall, skinTones: [.medium]), + "⛹🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manBouncingBall, skinTones: [.mediumDark]), + "⛹🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanBouncingBall, skinTones: [.mediumDark]), + "⛹🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manBouncingBall, skinTones: [.dark]), + "⛹🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanBouncingBall, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2549(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🇦🇨": EmojiWithSkinTones(baseEmoji: .flagAc, skinTones: nil), + "🇦🇩": EmojiWithSkinTones(baseEmoji: .flagAd, skinTones: nil), + "🇦🇪": EmojiWithSkinTones(baseEmoji: .flagAe, skinTones: nil), + "🇦🇫": EmojiWithSkinTones(baseEmoji: .flagAf, skinTones: nil), + "🇦🇬": EmojiWithSkinTones(baseEmoji: .flagAg, skinTones: nil), + "🇦🇮": EmojiWithSkinTones(baseEmoji: .flagAi, skinTones: nil), + "🇦🇱": EmojiWithSkinTones(baseEmoji: .flagAl, skinTones: nil), + "🇦🇲": EmojiWithSkinTones(baseEmoji: .flagAm, skinTones: nil), + "🇦🇴": EmojiWithSkinTones(baseEmoji: .flagAo, skinTones: nil), + "🇦🇶": EmojiWithSkinTones(baseEmoji: .flagAq, skinTones: nil), + "🇦🇷": EmojiWithSkinTones(baseEmoji: .flagAr, skinTones: nil), + "🇦🇸": EmojiWithSkinTones(baseEmoji: .flagAs, skinTones: nil), + "🇦🇹": EmojiWithSkinTones(baseEmoji: .flagAt, skinTones: nil), + "🇦🇺": EmojiWithSkinTones(baseEmoji: .flagAu, skinTones: nil), + "🇦🇼": EmojiWithSkinTones(baseEmoji: .flagAw, skinTones: nil), + "🇦🇽": EmojiWithSkinTones(baseEmoji: .flagAx, skinTones: nil), + "🇦🇿": EmojiWithSkinTones(baseEmoji: .flagAz, skinTones: nil), + "🇧🇦": EmojiWithSkinTones(baseEmoji: .flagBa, skinTones: nil), + "🇧🇧": EmojiWithSkinTones(baseEmoji: .flagBb, skinTones: nil), + "🇧🇩": EmojiWithSkinTones(baseEmoji: .flagBd, skinTones: nil), + "🇧🇪": EmojiWithSkinTones(baseEmoji: .flagBe, skinTones: nil), + "🇧🇫": EmojiWithSkinTones(baseEmoji: .flagBf, skinTones: nil), + "🇧🇬": EmojiWithSkinTones(baseEmoji: .flagBg, skinTones: nil), + "🇧🇭": EmojiWithSkinTones(baseEmoji: .flagBh, skinTones: nil), + "🇧🇮": EmojiWithSkinTones(baseEmoji: .flagBi, skinTones: nil), + "🇧🇯": EmojiWithSkinTones(baseEmoji: .flagBj, skinTones: nil), + "🇧🇱": EmojiWithSkinTones(baseEmoji: .flagBl, skinTones: nil), + "🇧🇲": EmojiWithSkinTones(baseEmoji: .flagBm, skinTones: nil), + "🇧🇳": EmojiWithSkinTones(baseEmoji: .flagBn, skinTones: nil), + "🇧🇴": EmojiWithSkinTones(baseEmoji: .flagBo, skinTones: nil), + "🇧🇶": EmojiWithSkinTones(baseEmoji: .flagBq, skinTones: nil), + "🇧🇷": EmojiWithSkinTones(baseEmoji: .flagBr, skinTones: nil), + "🇧🇸": EmojiWithSkinTones(baseEmoji: .flagBs, skinTones: nil), + "🇧🇹": EmojiWithSkinTones(baseEmoji: .flagBt, skinTones: nil), + "🇧🇻": EmojiWithSkinTones(baseEmoji: .flagBv, skinTones: nil), + "🇧🇼": EmojiWithSkinTones(baseEmoji: .flagBw, skinTones: nil), + "🇧🇾": EmojiWithSkinTones(baseEmoji: .flagBy, skinTones: nil), + "🇧🇿": EmojiWithSkinTones(baseEmoji: .flagBz, skinTones: nil), + "🇨🇦": EmojiWithSkinTones(baseEmoji: .flagCa, skinTones: nil), + "🇨🇨": EmojiWithSkinTones(baseEmoji: .flagCc, skinTones: nil), + "🇨🇩": EmojiWithSkinTones(baseEmoji: .flagCd, skinTones: nil), + "🇨🇫": EmojiWithSkinTones(baseEmoji: .flagCf, skinTones: nil), + "🇨🇬": EmojiWithSkinTones(baseEmoji: .flagCg, skinTones: nil), + "🇨🇭": EmojiWithSkinTones(baseEmoji: .flagCh, skinTones: nil), + "🇨🇮": EmojiWithSkinTones(baseEmoji: .flagCi, skinTones: nil), + "🇨🇰": EmojiWithSkinTones(baseEmoji: .flagCk, skinTones: nil), + "🇨🇱": EmojiWithSkinTones(baseEmoji: .flagCl, skinTones: nil), + "🇨🇲": EmojiWithSkinTones(baseEmoji: .flagCm, skinTones: nil), + "🇨🇳": EmojiWithSkinTones(baseEmoji: .cn, skinTones: nil), + "🇨🇴": EmojiWithSkinTones(baseEmoji: .flagCo, skinTones: nil), + "🇨🇵": EmojiWithSkinTones(baseEmoji: .flagCp, skinTones: nil), + "🇨🇷": EmojiWithSkinTones(baseEmoji: .flagCr, skinTones: nil), + "🇨🇺": EmojiWithSkinTones(baseEmoji: .flagCu, skinTones: nil), + "🇨🇻": EmojiWithSkinTones(baseEmoji: .flagCv, skinTones: nil), + "🇨🇼": EmojiWithSkinTones(baseEmoji: .flagCw, skinTones: nil), + "🇨🇽": EmojiWithSkinTones(baseEmoji: .flagCx, skinTones: nil), + "🇨🇾": EmojiWithSkinTones(baseEmoji: .flagCy, skinTones: nil), + "🇨🇿": EmojiWithSkinTones(baseEmoji: .flagCz, skinTones: nil), + "🇩🇪": EmojiWithSkinTones(baseEmoji: .de, skinTones: nil), + "🇩🇬": EmojiWithSkinTones(baseEmoji: .flagDg, skinTones: nil), + "🇩🇯": EmojiWithSkinTones(baseEmoji: .flagDj, skinTones: nil), + "🇩🇰": EmojiWithSkinTones(baseEmoji: .flagDk, skinTones: nil), + "🇩🇲": EmojiWithSkinTones(baseEmoji: .flagDm, skinTones: nil), + "🇩🇴": EmojiWithSkinTones(baseEmoji: .flagDo, skinTones: nil), + "🇩🇿": EmojiWithSkinTones(baseEmoji: .flagDz, skinTones: nil), + "🇪🇦": EmojiWithSkinTones(baseEmoji: .flagEa, skinTones: nil), + "🇪🇨": EmojiWithSkinTones(baseEmoji: .flagEc, skinTones: nil), + "🇪🇪": EmojiWithSkinTones(baseEmoji: .flagEe, skinTones: nil), + "🇪🇬": EmojiWithSkinTones(baseEmoji: .flagEg, skinTones: nil), + "🇪🇭": EmojiWithSkinTones(baseEmoji: .flagEh, skinTones: nil), + "🇪🇷": EmojiWithSkinTones(baseEmoji: .flagEr, skinTones: nil), + "🇪🇸": EmojiWithSkinTones(baseEmoji: .es, skinTones: nil), + "🇪🇹": EmojiWithSkinTones(baseEmoji: .flagEt, skinTones: nil), + "🇪🇺": EmojiWithSkinTones(baseEmoji: .flagEu, skinTones: nil), + "🇫🇮": EmojiWithSkinTones(baseEmoji: .flagFi, skinTones: nil), + "🇫🇯": EmojiWithSkinTones(baseEmoji: .flagFj, skinTones: nil), + "🇫🇰": EmojiWithSkinTones(baseEmoji: .flagFk, skinTones: nil), + "🇫🇲": EmojiWithSkinTones(baseEmoji: .flagFm, skinTones: nil), + "🇫🇴": EmojiWithSkinTones(baseEmoji: .flagFo, skinTones: nil), + "🇫🇷": EmojiWithSkinTones(baseEmoji: .fr, skinTones: nil), + "🇬🇦": EmojiWithSkinTones(baseEmoji: .flagGa, skinTones: nil), + "🇬🇧": EmojiWithSkinTones(baseEmoji: .gb, skinTones: nil), + "🇬🇩": EmojiWithSkinTones(baseEmoji: .flagGd, skinTones: nil), + "🇬🇪": EmojiWithSkinTones(baseEmoji: .flagGe, skinTones: nil), + "🇬🇫": EmojiWithSkinTones(baseEmoji: .flagGf, skinTones: nil), + "🇬🇬": EmojiWithSkinTones(baseEmoji: .flagGg, skinTones: nil), + "🇬🇭": EmojiWithSkinTones(baseEmoji: .flagGh, skinTones: nil), + "🇬🇮": EmojiWithSkinTones(baseEmoji: .flagGi, skinTones: nil), + "🇬🇱": EmojiWithSkinTones(baseEmoji: .flagGl, skinTones: nil), + "🇬🇲": EmojiWithSkinTones(baseEmoji: .flagGm, skinTones: nil), + "🇬🇳": EmojiWithSkinTones(baseEmoji: .flagGn, skinTones: nil), + "🇬🇵": EmojiWithSkinTones(baseEmoji: .flagGp, skinTones: nil), + "🇬🇶": EmojiWithSkinTones(baseEmoji: .flagGq, skinTones: nil), + "🇬🇷": EmojiWithSkinTones(baseEmoji: .flagGr, skinTones: nil), + "🇬🇸": EmojiWithSkinTones(baseEmoji: .flagGs, skinTones: nil), + "🇬🇹": EmojiWithSkinTones(baseEmoji: .flagGt, skinTones: nil), + "🇬🇺": EmojiWithSkinTones(baseEmoji: .flagGu, skinTones: nil), + "🇬🇼": EmojiWithSkinTones(baseEmoji: .flagGw, skinTones: nil), + "🇬🇾": EmojiWithSkinTones(baseEmoji: .flagGy, skinTones: nil), + "🇭🇰": EmojiWithSkinTones(baseEmoji: .flagHk, skinTones: nil), + "🇭🇲": EmojiWithSkinTones(baseEmoji: .flagHm, skinTones: nil), + "🇭🇳": EmojiWithSkinTones(baseEmoji: .flagHn, skinTones: nil), + "🇭🇷": EmojiWithSkinTones(baseEmoji: .flagHr, skinTones: nil), + "🇭🇹": EmojiWithSkinTones(baseEmoji: .flagHt, skinTones: nil), + "🇭🇺": EmojiWithSkinTones(baseEmoji: .flagHu, skinTones: nil), + "🇮🇨": EmojiWithSkinTones(baseEmoji: .flagIc, skinTones: nil), + "🇮🇩": EmojiWithSkinTones(baseEmoji: .flagId, skinTones: nil), + "🇮🇪": EmojiWithSkinTones(baseEmoji: .flagIe, skinTones: nil), + "🇮🇱": EmojiWithSkinTones(baseEmoji: .flagIl, skinTones: nil), + "🇮🇲": EmojiWithSkinTones(baseEmoji: .flagIm, skinTones: nil), + "🇮🇳": EmojiWithSkinTones(baseEmoji: .flagIn, skinTones: nil), + "🇮🇴": EmojiWithSkinTones(baseEmoji: .flagIo, skinTones: nil), + "🇮🇶": EmojiWithSkinTones(baseEmoji: .flagIq, skinTones: nil), + "🇮🇷": EmojiWithSkinTones(baseEmoji: .flagIr, skinTones: nil), + "🇮🇸": EmojiWithSkinTones(baseEmoji: .flagIs, skinTones: nil), + "🇮🇹": EmojiWithSkinTones(baseEmoji: .it, skinTones: nil), + "🇯🇪": EmojiWithSkinTones(baseEmoji: .flagJe, skinTones: nil), + "🇯🇲": EmojiWithSkinTones(baseEmoji: .flagJm, skinTones: nil), + "🇯🇴": EmojiWithSkinTones(baseEmoji: .flagJo, skinTones: nil), + "🇯🇵": EmojiWithSkinTones(baseEmoji: .jp, skinTones: nil), + "🇰🇪": EmojiWithSkinTones(baseEmoji: .flagKe, skinTones: nil), + "🇰🇬": EmojiWithSkinTones(baseEmoji: .flagKg, skinTones: nil), + "🇰🇭": EmojiWithSkinTones(baseEmoji: .flagKh, skinTones: nil), + "🇰🇮": EmojiWithSkinTones(baseEmoji: .flagKi, skinTones: nil), + "🇰🇲": EmojiWithSkinTones(baseEmoji: .flagKm, skinTones: nil), + "🇰🇳": EmojiWithSkinTones(baseEmoji: .flagKn, skinTones: nil), + "🇰🇵": EmojiWithSkinTones(baseEmoji: .flagKp, skinTones: nil), + "🇰🇷": EmojiWithSkinTones(baseEmoji: .kr, skinTones: nil), + "🇰🇼": EmojiWithSkinTones(baseEmoji: .flagKw, skinTones: nil), + "🇰🇾": EmojiWithSkinTones(baseEmoji: .flagKy, skinTones: nil), + "🇰🇿": EmojiWithSkinTones(baseEmoji: .flagKz, skinTones: nil), + "🇱🇦": EmojiWithSkinTones(baseEmoji: .flagLa, skinTones: nil), + "🇱🇧": EmojiWithSkinTones(baseEmoji: .flagLb, skinTones: nil), + "🇱🇨": EmojiWithSkinTones(baseEmoji: .flagLc, skinTones: nil), + "🇱🇮": EmojiWithSkinTones(baseEmoji: .flagLi, skinTones: nil), + "🇱🇰": EmojiWithSkinTones(baseEmoji: .flagLk, skinTones: nil), + "🇱🇷": EmojiWithSkinTones(baseEmoji: .flagLr, skinTones: nil), + "🇱🇸": EmojiWithSkinTones(baseEmoji: .flagLs, skinTones: nil), + "🇱🇹": EmojiWithSkinTones(baseEmoji: .flagLt, skinTones: nil), + "🇱🇺": EmojiWithSkinTones(baseEmoji: .flagLu, skinTones: nil), + "🇱🇻": EmojiWithSkinTones(baseEmoji: .flagLv, skinTones: nil), + "🇱🇾": EmojiWithSkinTones(baseEmoji: .flagLy, skinTones: nil), + "🇲🇦": EmojiWithSkinTones(baseEmoji: .flagMa, skinTones: nil), + "🇲🇨": EmojiWithSkinTones(baseEmoji: .flagMc, skinTones: nil), + "🇲🇩": EmojiWithSkinTones(baseEmoji: .flagMd, skinTones: nil), + "🇲🇪": EmojiWithSkinTones(baseEmoji: .flagMe, skinTones: nil), + "🇲🇫": EmojiWithSkinTones(baseEmoji: .flagMf, skinTones: nil), + "🇲🇬": EmojiWithSkinTones(baseEmoji: .flagMg, skinTones: nil), + "🇲🇭": EmojiWithSkinTones(baseEmoji: .flagMh, skinTones: nil), + "🇲🇰": EmojiWithSkinTones(baseEmoji: .flagMk, skinTones: nil), + "🇲🇱": EmojiWithSkinTones(baseEmoji: .flagMl, skinTones: nil), + "🇲🇲": EmojiWithSkinTones(baseEmoji: .flagMm, skinTones: nil), + "🇲🇳": EmojiWithSkinTones(baseEmoji: .flagMn, skinTones: nil), + "🇲🇴": EmojiWithSkinTones(baseEmoji: .flagMo, skinTones: nil), + "🇲🇵": EmojiWithSkinTones(baseEmoji: .flagMp, skinTones: nil), + "🇲🇶": EmojiWithSkinTones(baseEmoji: .flagMq, skinTones: nil), + "🇲🇷": EmojiWithSkinTones(baseEmoji: .flagMr, skinTones: nil), + "🇲🇸": EmojiWithSkinTones(baseEmoji: .flagMs, skinTones: nil), + "🇲🇹": EmojiWithSkinTones(baseEmoji: .flagMt, skinTones: nil), + "🇲🇺": EmojiWithSkinTones(baseEmoji: .flagMu, skinTones: nil), + "🇲🇻": EmojiWithSkinTones(baseEmoji: .flagMv, skinTones: nil), + "🇲🇼": EmojiWithSkinTones(baseEmoji: .flagMw, skinTones: nil), + "🇲🇽": EmojiWithSkinTones(baseEmoji: .flagMx, skinTones: nil), + "🇲🇾": EmojiWithSkinTones(baseEmoji: .flagMy, skinTones: nil), + "🇲🇿": EmojiWithSkinTones(baseEmoji: .flagMz, skinTones: nil), + "🇳🇦": EmojiWithSkinTones(baseEmoji: .flagNa, skinTones: nil), + "🇳🇨": EmojiWithSkinTones(baseEmoji: .flagNc, skinTones: nil), + "🇳🇪": EmojiWithSkinTones(baseEmoji: .flagNe, skinTones: nil), + "🇳🇫": EmojiWithSkinTones(baseEmoji: .flagNf, skinTones: nil), + "🇳🇬": EmojiWithSkinTones(baseEmoji: .flagNg, skinTones: nil), + "🇳🇮": EmojiWithSkinTones(baseEmoji: .flagNi, skinTones: nil), + "🇳🇱": EmojiWithSkinTones(baseEmoji: .flagNl, skinTones: nil), + "🇳🇴": EmojiWithSkinTones(baseEmoji: .flagNo, skinTones: nil), + "🇳🇵": EmojiWithSkinTones(baseEmoji: .flagNp, skinTones: nil), + "🇳🇷": EmojiWithSkinTones(baseEmoji: .flagNr, skinTones: nil), + "🇳🇺": EmojiWithSkinTones(baseEmoji: .flagNu, skinTones: nil), + "🇳🇿": EmojiWithSkinTones(baseEmoji: .flagNz, skinTones: nil), + "🇴🇲": EmojiWithSkinTones(baseEmoji: .flagOm, skinTones: nil), + "🇵🇦": EmojiWithSkinTones(baseEmoji: .flagPa, skinTones: nil), + "🇵🇪": EmojiWithSkinTones(baseEmoji: .flagPe, skinTones: nil), + "🇵🇫": EmojiWithSkinTones(baseEmoji: .flagPf, skinTones: nil), + "🇵🇬": EmojiWithSkinTones(baseEmoji: .flagPg, skinTones: nil), + "🇵🇭": EmojiWithSkinTones(baseEmoji: .flagPh, skinTones: nil), + "🇵🇰": EmojiWithSkinTones(baseEmoji: .flagPk, skinTones: nil), + "🇵🇱": EmojiWithSkinTones(baseEmoji: .flagPl, skinTones: nil), + "🇵🇲": EmojiWithSkinTones(baseEmoji: .flagPm, skinTones: nil), + "🇵🇳": EmojiWithSkinTones(baseEmoji: .flagPn, skinTones: nil), + "🇵🇷": EmojiWithSkinTones(baseEmoji: .flagPr, skinTones: nil), + "🇵🇸": EmojiWithSkinTones(baseEmoji: .flagPs, skinTones: nil), + "🇵🇹": EmojiWithSkinTones(baseEmoji: .flagPt, skinTones: nil), + "🇵🇼": EmojiWithSkinTones(baseEmoji: .flagPw, skinTones: nil), + "🇵🇾": EmojiWithSkinTones(baseEmoji: .flagPy, skinTones: nil), + "🇶🇦": EmojiWithSkinTones(baseEmoji: .flagQa, skinTones: nil), + "🇷🇪": EmojiWithSkinTones(baseEmoji: .flagRe, skinTones: nil), + "🇷🇴": EmojiWithSkinTones(baseEmoji: .flagRo, skinTones: nil), + "🇷🇸": EmojiWithSkinTones(baseEmoji: .flagRs, skinTones: nil), + "🇷🇺": EmojiWithSkinTones(baseEmoji: .ru, skinTones: nil), + "🇷🇼": EmojiWithSkinTones(baseEmoji: .flagRw, skinTones: nil), + "🇸🇦": EmojiWithSkinTones(baseEmoji: .flagSa, skinTones: nil), + "🇸🇧": EmojiWithSkinTones(baseEmoji: .flagSb, skinTones: nil), + "🇸🇨": EmojiWithSkinTones(baseEmoji: .flagSc, skinTones: nil), + "🇸🇩": EmojiWithSkinTones(baseEmoji: .flagSd, skinTones: nil), + "🇸🇪": EmojiWithSkinTones(baseEmoji: .flagSe, skinTones: nil), + "🇸🇬": EmojiWithSkinTones(baseEmoji: .flagSg, skinTones: nil), + "🇸🇭": EmojiWithSkinTones(baseEmoji: .flagSh, skinTones: nil), + "🇸🇮": EmojiWithSkinTones(baseEmoji: .flagSi, skinTones: nil), + "🇸🇯": EmojiWithSkinTones(baseEmoji: .flagSj, skinTones: nil), + "🇸🇰": EmojiWithSkinTones(baseEmoji: .flagSk, skinTones: nil), + "🇸🇱": EmojiWithSkinTones(baseEmoji: .flagSl, skinTones: nil), + "🇸🇲": EmojiWithSkinTones(baseEmoji: .flagSm, skinTones: nil), + "🇸🇳": EmojiWithSkinTones(baseEmoji: .flagSn, skinTones: nil), + "🇸🇴": EmojiWithSkinTones(baseEmoji: .flagSo, skinTones: nil), + "🇸🇷": EmojiWithSkinTones(baseEmoji: .flagSr, skinTones: nil), + "🇸🇸": EmojiWithSkinTones(baseEmoji: .flagSs, skinTones: nil), + "🇸🇹": EmojiWithSkinTones(baseEmoji: .flagSt, skinTones: nil), + "🇸🇻": EmojiWithSkinTones(baseEmoji: .flagSv, skinTones: nil), + "🇸🇽": EmojiWithSkinTones(baseEmoji: .flagSx, skinTones: nil), + "🇸🇾": EmojiWithSkinTones(baseEmoji: .flagSy, skinTones: nil), + "🇸🇿": EmojiWithSkinTones(baseEmoji: .flagSz, skinTones: nil), + "🇹🇦": EmojiWithSkinTones(baseEmoji: .flagTa, skinTones: nil), + "🇹🇨": EmojiWithSkinTones(baseEmoji: .flagTc, skinTones: nil), + "🇹🇩": EmojiWithSkinTones(baseEmoji: .flagTd, skinTones: nil), + "🇹🇫": EmojiWithSkinTones(baseEmoji: .flagTf, skinTones: nil), + "🇹🇬": EmojiWithSkinTones(baseEmoji: .flagTg, skinTones: nil), + "🇹🇭": EmojiWithSkinTones(baseEmoji: .flagTh, skinTones: nil), + "🇹🇯": EmojiWithSkinTones(baseEmoji: .flagTj, skinTones: nil), + "🇹🇰": EmojiWithSkinTones(baseEmoji: .flagTk, skinTones: nil), + "🇹🇱": EmojiWithSkinTones(baseEmoji: .flagTl, skinTones: nil), + "🇹🇲": EmojiWithSkinTones(baseEmoji: .flagTm, skinTones: nil), + "🇹🇳": EmojiWithSkinTones(baseEmoji: .flagTn, skinTones: nil), + "🇹🇴": EmojiWithSkinTones(baseEmoji: .flagTo, skinTones: nil), + "🇹🇷": EmojiWithSkinTones(baseEmoji: .flagTr, skinTones: nil), + "🇹🇹": EmojiWithSkinTones(baseEmoji: .flagTt, skinTones: nil), + "🇹🇻": EmojiWithSkinTones(baseEmoji: .flagTv, skinTones: nil), + "🇹🇼": EmojiWithSkinTones(baseEmoji: .flagTw, skinTones: nil), + "🇹🇿": EmojiWithSkinTones(baseEmoji: .flagTz, skinTones: nil), + "🇺🇦": EmojiWithSkinTones(baseEmoji: .flagUa, skinTones: nil), + "🇺🇬": EmojiWithSkinTones(baseEmoji: .flagUg, skinTones: nil), + "🇺🇲": EmojiWithSkinTones(baseEmoji: .flagUm, skinTones: nil), + "🇺🇳": EmojiWithSkinTones(baseEmoji: .flagUn, skinTones: nil), + "🇺🇸": EmojiWithSkinTones(baseEmoji: .us, skinTones: nil), + "🇺🇾": EmojiWithSkinTones(baseEmoji: .flagUy, skinTones: nil), + "🇺🇿": EmojiWithSkinTones(baseEmoji: .flagUz, skinTones: nil), + "🇻🇦": EmojiWithSkinTones(baseEmoji: .flagVa, skinTones: nil), + "🇻🇨": EmojiWithSkinTones(baseEmoji: .flagVc, skinTones: nil), + "🇻🇪": EmojiWithSkinTones(baseEmoji: .flagVe, skinTones: nil), + "🇻🇬": EmojiWithSkinTones(baseEmoji: .flagVg, skinTones: nil), + "🇻🇮": EmojiWithSkinTones(baseEmoji: .flagVi, skinTones: nil), + "🇻🇳": EmojiWithSkinTones(baseEmoji: .flagVn, skinTones: nil), + "🇻🇺": EmojiWithSkinTones(baseEmoji: .flagVu, skinTones: nil), + "🇼🇫": EmojiWithSkinTones(baseEmoji: .flagWf, skinTones: nil), + "🇼🇸": EmojiWithSkinTones(baseEmoji: .flagWs, skinTones: nil), + "🇽🇰": EmojiWithSkinTones(baseEmoji: .flagXk, skinTones: nil), + "🇾🇪": EmojiWithSkinTones(baseEmoji: .flagYe, skinTones: nil), + "🇾🇹": EmojiWithSkinTones(baseEmoji: .flagYt, skinTones: nil), + "🇿🇦": EmojiWithSkinTones(baseEmoji: .flagZa, skinTones: nil), + "🇿🇲": EmojiWithSkinTones(baseEmoji: .flagZm, skinTones: nil), + "🇿🇼": EmojiWithSkinTones(baseEmoji: .flagZw, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2558(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🎅🏻": EmojiWithSkinTones(baseEmoji: .santa, skinTones: [.light]), + "🎅🏼": EmojiWithSkinTones(baseEmoji: .santa, skinTones: [.mediumLight]), + "🎅🏽": EmojiWithSkinTones(baseEmoji: .santa, skinTones: [.medium]), + "🎅🏾": EmojiWithSkinTones(baseEmoji: .santa, skinTones: [.mediumDark]), + "🎅🏿": EmojiWithSkinTones(baseEmoji: .santa, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2559(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏃🏻": EmojiWithSkinTones(baseEmoji: .runner, skinTones: [.light]), + "🏇🏻": EmojiWithSkinTones(baseEmoji: .horseRacing, skinTones: [.light]), + "🏂🏻": EmojiWithSkinTones(baseEmoji: .snowboarder, skinTones: [.light]), + "🏌🏻": EmojiWithSkinTones(baseEmoji: .golfer, skinTones: [.light]), + "🏄🏻": EmojiWithSkinTones(baseEmoji: .surfer, skinTones: [.light]), + "🏊🏻": EmojiWithSkinTones(baseEmoji: .swimmer, skinTones: [.light]), + "🏋🏻": EmojiWithSkinTones(baseEmoji: .weightLifter, skinTones: [.light]), + "🏃🏼": EmojiWithSkinTones(baseEmoji: .runner, skinTones: [.mediumLight]), + "🏇🏼": EmojiWithSkinTones(baseEmoji: .horseRacing, skinTones: [.mediumLight]), + "🏂🏼": EmojiWithSkinTones(baseEmoji: .snowboarder, skinTones: [.mediumLight]), + "🏌🏼": EmojiWithSkinTones(baseEmoji: .golfer, skinTones: [.mediumLight]), + "🏄🏼": EmojiWithSkinTones(baseEmoji: .surfer, skinTones: [.mediumLight]), + "🏊🏼": EmojiWithSkinTones(baseEmoji: .swimmer, skinTones: [.mediumLight]), + "🏋🏼": EmojiWithSkinTones(baseEmoji: .weightLifter, skinTones: [.mediumLight]), + "🏃🏽": EmojiWithSkinTones(baseEmoji: .runner, skinTones: [.medium]), + "🏇🏽": EmojiWithSkinTones(baseEmoji: .horseRacing, skinTones: [.medium]), + "🏂🏽": EmojiWithSkinTones(baseEmoji: .snowboarder, skinTones: [.medium]), + "🏌🏽": EmojiWithSkinTones(baseEmoji: .golfer, skinTones: [.medium]), + "🏄🏽": EmojiWithSkinTones(baseEmoji: .surfer, skinTones: [.medium]), + "🏊🏽": EmojiWithSkinTones(baseEmoji: .swimmer, skinTones: [.medium]), + "🏋🏽": EmojiWithSkinTones(baseEmoji: .weightLifter, skinTones: [.medium]), + "🏃🏾": EmojiWithSkinTones(baseEmoji: .runner, skinTones: [.mediumDark]), + "🏇🏾": EmojiWithSkinTones(baseEmoji: .horseRacing, skinTones: [.mediumDark]), + "🏂🏾": EmojiWithSkinTones(baseEmoji: .snowboarder, skinTones: [.mediumDark]), + "🏌🏾": EmojiWithSkinTones(baseEmoji: .golfer, skinTones: [.mediumDark]), + "🏄🏾": EmojiWithSkinTones(baseEmoji: .surfer, skinTones: [.mediumDark]), + "🏊🏾": EmojiWithSkinTones(baseEmoji: .swimmer, skinTones: [.mediumDark]), + "🏋🏾": EmojiWithSkinTones(baseEmoji: .weightLifter, skinTones: [.mediumDark]), + "🏃🏿": EmojiWithSkinTones(baseEmoji: .runner, skinTones: [.dark]), + "🏇🏿": EmojiWithSkinTones(baseEmoji: .horseRacing, skinTones: [.dark]), + "🏂🏿": EmojiWithSkinTones(baseEmoji: .snowboarder, skinTones: [.dark]), + "🏌🏿": EmojiWithSkinTones(baseEmoji: .golfer, skinTones: [.dark]), + "🏄🏿": EmojiWithSkinTones(baseEmoji: .surfer, skinTones: [.dark]), + "🏊🏿": EmojiWithSkinTones(baseEmoji: .swimmer, skinTones: [.dark]), + "🏋🏿": EmojiWithSkinTones(baseEmoji: .weightLifter, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2560(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👋🏻": EmojiWithSkinTones(baseEmoji: .wave, skinTones: [.light]), + "👌🏻": EmojiWithSkinTones(baseEmoji: .okHand, skinTones: [.light]), + "👈🏻": EmojiWithSkinTones(baseEmoji: .pointLeft, skinTones: [.light]), + "👉🏻": EmojiWithSkinTones(baseEmoji: .pointRight, skinTones: [.light]), + "👆🏻": EmojiWithSkinTones(baseEmoji: .pointUp2, skinTones: [.light]), + "👇🏻": EmojiWithSkinTones(baseEmoji: .pointDown, skinTones: [.light]), + "👍🏻": EmojiWithSkinTones(baseEmoji: .plusOne, skinTones: [.light]), + "👎🏻": EmojiWithSkinTones(baseEmoji: .negativeOne, skinTones: [.light]), + "👊🏻": EmojiWithSkinTones(baseEmoji: .facepunch, skinTones: [.light]), + "👏🏻": EmojiWithSkinTones(baseEmoji: .clap, skinTones: [.light]), + "👐🏻": EmojiWithSkinTones(baseEmoji: .openHands, skinTones: [.light]), + "👂🏻": EmojiWithSkinTones(baseEmoji: .ear, skinTones: [.light]), + "👃🏻": EmojiWithSkinTones(baseEmoji: .nose, skinTones: [.light]), + "👦🏻": EmojiWithSkinTones(baseEmoji: .boy, skinTones: [.light]), + "👧🏻": EmojiWithSkinTones(baseEmoji: .girl, skinTones: [.light]), + "👨🏻": EmojiWithSkinTones(baseEmoji: .man, skinTones: [.light]), + "👋🏼": EmojiWithSkinTones(baseEmoji: .wave, skinTones: [.mediumLight]), + "👌🏼": EmojiWithSkinTones(baseEmoji: .okHand, skinTones: [.mediumLight]), + "👈🏼": EmojiWithSkinTones(baseEmoji: .pointLeft, skinTones: [.mediumLight]), + "👉🏼": EmojiWithSkinTones(baseEmoji: .pointRight, skinTones: [.mediumLight]), + "👆🏼": EmojiWithSkinTones(baseEmoji: .pointUp2, skinTones: [.mediumLight]), + "👇🏼": EmojiWithSkinTones(baseEmoji: .pointDown, skinTones: [.mediumLight]), + "👍🏼": EmojiWithSkinTones(baseEmoji: .plusOne, skinTones: [.mediumLight]), + "👎🏼": EmojiWithSkinTones(baseEmoji: .negativeOne, skinTones: [.mediumLight]), + "👊🏼": EmojiWithSkinTones(baseEmoji: .facepunch, skinTones: [.mediumLight]), + "👏🏼": EmojiWithSkinTones(baseEmoji: .clap, skinTones: [.mediumLight]), + "👐🏼": EmojiWithSkinTones(baseEmoji: .openHands, skinTones: [.mediumLight]), + "👂🏼": EmojiWithSkinTones(baseEmoji: .ear, skinTones: [.mediumLight]), + "👃🏼": EmojiWithSkinTones(baseEmoji: .nose, skinTones: [.mediumLight]), + "👦🏼": EmojiWithSkinTones(baseEmoji: .boy, skinTones: [.mediumLight]), + "👧🏼": EmojiWithSkinTones(baseEmoji: .girl, skinTones: [.mediumLight]), + "👋🏽": EmojiWithSkinTones(baseEmoji: .wave, skinTones: [.medium]), + "👌🏽": EmojiWithSkinTones(baseEmoji: .okHand, skinTones: [.medium]), + "👈🏽": EmojiWithSkinTones(baseEmoji: .pointLeft, skinTones: [.medium]), + "👉🏽": EmojiWithSkinTones(baseEmoji: .pointRight, skinTones: [.medium]), + "👆🏽": EmojiWithSkinTones(baseEmoji: .pointUp2, skinTones: [.medium]), + "👇🏽": EmojiWithSkinTones(baseEmoji: .pointDown, skinTones: [.medium]), + "👍🏽": EmojiWithSkinTones(baseEmoji: .plusOne, skinTones: [.medium]), + "👎🏽": EmojiWithSkinTones(baseEmoji: .negativeOne, skinTones: [.medium]), + "👊🏽": EmojiWithSkinTones(baseEmoji: .facepunch, skinTones: [.medium]), + "👏🏽": EmojiWithSkinTones(baseEmoji: .clap, skinTones: [.medium]), + "👐🏽": EmojiWithSkinTones(baseEmoji: .openHands, skinTones: [.medium]), + "👂🏽": EmojiWithSkinTones(baseEmoji: .ear, skinTones: [.medium]), + "👃🏽": EmojiWithSkinTones(baseEmoji: .nose, skinTones: [.medium]), + "👦🏽": EmojiWithSkinTones(baseEmoji: .boy, skinTones: [.medium]), + "👋🏾": EmojiWithSkinTones(baseEmoji: .wave, skinTones: [.mediumDark]), + "👌🏾": EmojiWithSkinTones(baseEmoji: .okHand, skinTones: [.mediumDark]), + "👈🏾": EmojiWithSkinTones(baseEmoji: .pointLeft, skinTones: [.mediumDark]), + "👉🏾": EmojiWithSkinTones(baseEmoji: .pointRight, skinTones: [.mediumDark]), + "👆🏾": EmojiWithSkinTones(baseEmoji: .pointUp2, skinTones: [.mediumDark]), + "👇🏾": EmojiWithSkinTones(baseEmoji: .pointDown, skinTones: [.mediumDark]), + "👍🏾": EmojiWithSkinTones(baseEmoji: .plusOne, skinTones: [.mediumDark]), + "👎🏾": EmojiWithSkinTones(baseEmoji: .negativeOne, skinTones: [.mediumDark]), + "👊🏾": EmojiWithSkinTones(baseEmoji: .facepunch, skinTones: [.mediumDark]), + "👏🏾": EmojiWithSkinTones(baseEmoji: .clap, skinTones: [.mediumDark]), + "👐🏾": EmojiWithSkinTones(baseEmoji: .openHands, skinTones: [.mediumDark]), + "👂🏾": EmojiWithSkinTones(baseEmoji: .ear, skinTones: [.mediumDark]), + "👃🏾": EmojiWithSkinTones(baseEmoji: .nose, skinTones: [.mediumDark]), + "👋🏿": EmojiWithSkinTones(baseEmoji: .wave, skinTones: [.dark]), + "👌🏿": EmojiWithSkinTones(baseEmoji: .okHand, skinTones: [.dark]), + "👈🏿": EmojiWithSkinTones(baseEmoji: .pointLeft, skinTones: [.dark]), + "👉🏿": EmojiWithSkinTones(baseEmoji: .pointRight, skinTones: [.dark]), + "👆🏿": EmojiWithSkinTones(baseEmoji: .pointUp2, skinTones: [.dark]), + "👇🏿": EmojiWithSkinTones(baseEmoji: .pointDown, skinTones: [.dark]), + "👍🏿": EmojiWithSkinTones(baseEmoji: .plusOne, skinTones: [.dark]), + "👎🏿": EmojiWithSkinTones(baseEmoji: .negativeOne, skinTones: [.dark]), + "👊🏿": EmojiWithSkinTones(baseEmoji: .facepunch, skinTones: [.dark]), + "👏🏿": EmojiWithSkinTones(baseEmoji: .clap, skinTones: [.dark]), + "👐🏿": EmojiWithSkinTones(baseEmoji: .openHands, skinTones: [.dark]), + "👂🏿": EmojiWithSkinTones(baseEmoji: .ear, skinTones: [.dark]), + "👃🏿": EmojiWithSkinTones(baseEmoji: .nose, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2561(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "💅🏻": EmojiWithSkinTones(baseEmoji: .nailCare, skinTones: [.light]), + "💪🏻": EmojiWithSkinTones(baseEmoji: .muscle, skinTones: [.light]), + "👶🏻": EmojiWithSkinTones(baseEmoji: .baby, skinTones: [.light]), + "👱🏻": EmojiWithSkinTones(baseEmoji: .personWithBlondHair, skinTones: [.light]), + "👩🏻": EmojiWithSkinTones(baseEmoji: .woman, skinTones: [.light]), + "👴🏻": EmojiWithSkinTones(baseEmoji: .olderMan, skinTones: [.light]), + "👵🏻": EmojiWithSkinTones(baseEmoji: .olderWoman, skinTones: [.light]), + "💁🏻": EmojiWithSkinTones(baseEmoji: .informationDeskPerson, skinTones: [.light]), + "👮🏻": EmojiWithSkinTones(baseEmoji: .cop, skinTones: [.light]), + "💂🏻": EmojiWithSkinTones(baseEmoji: .guardsman, skinTones: [.light]), + "👷🏻": EmojiWithSkinTones(baseEmoji: .constructionWorker, skinTones: [.light]), + "👸🏻": EmojiWithSkinTones(baseEmoji: .princess, skinTones: [.light]), + "👳🏻": EmojiWithSkinTones(baseEmoji: .manWithTurban, skinTones: [.light]), + "👲🏻": EmojiWithSkinTones(baseEmoji: .manWithGuaPiMao, skinTones: [.light]), + "👰🏻": EmojiWithSkinTones(baseEmoji: .brideWithVeil, skinTones: [.light]), + "👼🏻": EmojiWithSkinTones(baseEmoji: .angel, skinTones: [.light]), + "💆🏻": EmojiWithSkinTones(baseEmoji: .massage, skinTones: [.light]), + "💇🏻": EmojiWithSkinTones(baseEmoji: .haircut, skinTones: [.light]), + "💃🏻": EmojiWithSkinTones(baseEmoji: .dancer, skinTones: [.light]), + "👭🏻": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.light]), + "👫🏻": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.light]), + "👬🏻": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.light]), + "💏🏻": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.light]), + "💑🏻": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.light]), + "💅🏼": EmojiWithSkinTones(baseEmoji: .nailCare, skinTones: [.mediumLight]), + "💪🏼": EmojiWithSkinTones(baseEmoji: .muscle, skinTones: [.mediumLight]), + "👶🏼": EmojiWithSkinTones(baseEmoji: .baby, skinTones: [.mediumLight]), + "👱🏼": EmojiWithSkinTones(baseEmoji: .personWithBlondHair, skinTones: [.mediumLight]), + "👨🏼": EmojiWithSkinTones(baseEmoji: .man, skinTones: [.mediumLight]), + "👩🏼": EmojiWithSkinTones(baseEmoji: .woman, skinTones: [.mediumLight]), + "👴🏼": EmojiWithSkinTones(baseEmoji: .olderMan, skinTones: [.mediumLight]), + "👵🏼": EmojiWithSkinTones(baseEmoji: .olderWoman, skinTones: [.mediumLight]), + "💁🏼": EmojiWithSkinTones(baseEmoji: .informationDeskPerson, skinTones: [.mediumLight]), + "👮🏼": EmojiWithSkinTones(baseEmoji: .cop, skinTones: [.mediumLight]), + "💂🏼": EmojiWithSkinTones(baseEmoji: .guardsman, skinTones: [.mediumLight]), + "👷🏼": EmojiWithSkinTones(baseEmoji: .constructionWorker, skinTones: [.mediumLight]), + "👸🏼": EmojiWithSkinTones(baseEmoji: .princess, skinTones: [.mediumLight]), + "👳🏼": EmojiWithSkinTones(baseEmoji: .manWithTurban, skinTones: [.mediumLight]), + "👲🏼": EmojiWithSkinTones(baseEmoji: .manWithGuaPiMao, skinTones: [.mediumLight]), + "👰🏼": EmojiWithSkinTones(baseEmoji: .brideWithVeil, skinTones: [.mediumLight]), + "👼🏼": EmojiWithSkinTones(baseEmoji: .angel, skinTones: [.mediumLight]), + "💆🏼": EmojiWithSkinTones(baseEmoji: .massage, skinTones: [.mediumLight]), + "💇🏼": EmojiWithSkinTones(baseEmoji: .haircut, skinTones: [.mediumLight]), + "💃🏼": EmojiWithSkinTones(baseEmoji: .dancer, skinTones: [.mediumLight]), + "👭🏼": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumLight]), + "👫🏼": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumLight]), + "👬🏼": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumLight]), + "💏🏼": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.mediumLight]), + "💑🏼": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.mediumLight]), + "💅🏽": EmojiWithSkinTones(baseEmoji: .nailCare, skinTones: [.medium]), + "💪🏽": EmojiWithSkinTones(baseEmoji: .muscle, skinTones: [.medium]), + "👶🏽": EmojiWithSkinTones(baseEmoji: .baby, skinTones: [.medium]), + "👧🏽": EmojiWithSkinTones(baseEmoji: .girl, skinTones: [.medium]), + "👱🏽": EmojiWithSkinTones(baseEmoji: .personWithBlondHair, skinTones: [.medium]), + "👨🏽": EmojiWithSkinTones(baseEmoji: .man, skinTones: [.medium]), + "👩🏽": EmojiWithSkinTones(baseEmoji: .woman, skinTones: [.medium]), + "👴🏽": EmojiWithSkinTones(baseEmoji: .olderMan, skinTones: [.medium]), + "👵🏽": EmojiWithSkinTones(baseEmoji: .olderWoman, skinTones: [.medium]), + "💁🏽": EmojiWithSkinTones(baseEmoji: .informationDeskPerson, skinTones: [.medium]), + "👮🏽": EmojiWithSkinTones(baseEmoji: .cop, skinTones: [.medium]), + "💂🏽": EmojiWithSkinTones(baseEmoji: .guardsman, skinTones: [.medium]), + "👷🏽": EmojiWithSkinTones(baseEmoji: .constructionWorker, skinTones: [.medium]), + "👸🏽": EmojiWithSkinTones(baseEmoji: .princess, skinTones: [.medium]), + "👳🏽": EmojiWithSkinTones(baseEmoji: .manWithTurban, skinTones: [.medium]), + "👲🏽": EmojiWithSkinTones(baseEmoji: .manWithGuaPiMao, skinTones: [.medium]), + "👰🏽": EmojiWithSkinTones(baseEmoji: .brideWithVeil, skinTones: [.medium]), + "👼🏽": EmojiWithSkinTones(baseEmoji: .angel, skinTones: [.medium]), + "💆🏽": EmojiWithSkinTones(baseEmoji: .massage, skinTones: [.medium]), + "💇🏽": EmojiWithSkinTones(baseEmoji: .haircut, skinTones: [.medium]), + "💃🏽": EmojiWithSkinTones(baseEmoji: .dancer, skinTones: [.medium]), + "👭🏽": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.medium]), + "👫🏽": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.medium]), + "👬🏽": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.medium]), + "💏🏽": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.medium]), + "💑🏽": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.medium]), + "💅🏾": EmojiWithSkinTones(baseEmoji: .nailCare, skinTones: [.mediumDark]), + "💪🏾": EmojiWithSkinTones(baseEmoji: .muscle, skinTones: [.mediumDark]), + "👶🏾": EmojiWithSkinTones(baseEmoji: .baby, skinTones: [.mediumDark]), + "👦🏾": EmojiWithSkinTones(baseEmoji: .boy, skinTones: [.mediumDark]), + "👧🏾": EmojiWithSkinTones(baseEmoji: .girl, skinTones: [.mediumDark]), + "👱🏾": EmojiWithSkinTones(baseEmoji: .personWithBlondHair, skinTones: [.mediumDark]), + "👨🏾": EmojiWithSkinTones(baseEmoji: .man, skinTones: [.mediumDark]), + "👩🏾": EmojiWithSkinTones(baseEmoji: .woman, skinTones: [.mediumDark]), + "👴🏾": EmojiWithSkinTones(baseEmoji: .olderMan, skinTones: [.mediumDark]), + "👵🏾": EmojiWithSkinTones(baseEmoji: .olderWoman, skinTones: [.mediumDark]), + "💁🏾": EmojiWithSkinTones(baseEmoji: .informationDeskPerson, skinTones: [.mediumDark]), + "👮🏾": EmojiWithSkinTones(baseEmoji: .cop, skinTones: [.mediumDark]), + "💂🏾": EmojiWithSkinTones(baseEmoji: .guardsman, skinTones: [.mediumDark]), + "👷🏾": EmojiWithSkinTones(baseEmoji: .constructionWorker, skinTones: [.mediumDark]), + "👸🏾": EmojiWithSkinTones(baseEmoji: .princess, skinTones: [.mediumDark]), + "👳🏾": EmojiWithSkinTones(baseEmoji: .manWithTurban, skinTones: [.mediumDark]), + "👲🏾": EmojiWithSkinTones(baseEmoji: .manWithGuaPiMao, skinTones: [.mediumDark]), + "👰🏾": EmojiWithSkinTones(baseEmoji: .brideWithVeil, skinTones: [.mediumDark]), + "👼🏾": EmojiWithSkinTones(baseEmoji: .angel, skinTones: [.mediumDark]), + "💆🏾": EmojiWithSkinTones(baseEmoji: .massage, skinTones: [.mediumDark]), + "💇🏾": EmojiWithSkinTones(baseEmoji: .haircut, skinTones: [.mediumDark]), + "💃🏾": EmojiWithSkinTones(baseEmoji: .dancer, skinTones: [.mediumDark]), + "👭🏾": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumDark]), + "👫🏾": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumDark]), + "👬🏾": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumDark]), + "💏🏾": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.mediumDark]), + "💑🏾": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.mediumDark]), + "💅🏿": EmojiWithSkinTones(baseEmoji: .nailCare, skinTones: [.dark]), + "💪🏿": EmojiWithSkinTones(baseEmoji: .muscle, skinTones: [.dark]), + "👶🏿": EmojiWithSkinTones(baseEmoji: .baby, skinTones: [.dark]), + "👦🏿": EmojiWithSkinTones(baseEmoji: .boy, skinTones: [.dark]), + "👧🏿": EmojiWithSkinTones(baseEmoji: .girl, skinTones: [.dark]), + "👱🏿": EmojiWithSkinTones(baseEmoji: .personWithBlondHair, skinTones: [.dark]), + "👨🏿": EmojiWithSkinTones(baseEmoji: .man, skinTones: [.dark]), + "👩🏿": EmojiWithSkinTones(baseEmoji: .woman, skinTones: [.dark]), + "👴🏿": EmojiWithSkinTones(baseEmoji: .olderMan, skinTones: [.dark]), + "👵🏿": EmojiWithSkinTones(baseEmoji: .olderWoman, skinTones: [.dark]), + "💁🏿": EmojiWithSkinTones(baseEmoji: .informationDeskPerson, skinTones: [.dark]), + "👮🏿": EmojiWithSkinTones(baseEmoji: .cop, skinTones: [.dark]), + "💂🏿": EmojiWithSkinTones(baseEmoji: .guardsman, skinTones: [.dark]), + "👷🏿": EmojiWithSkinTones(baseEmoji: .constructionWorker, skinTones: [.dark]), + "👸🏿": EmojiWithSkinTones(baseEmoji: .princess, skinTones: [.dark]), + "👳🏿": EmojiWithSkinTones(baseEmoji: .manWithTurban, skinTones: [.dark]), + "👲🏿": EmojiWithSkinTones(baseEmoji: .manWithGuaPiMao, skinTones: [.dark]), + "👰🏿": EmojiWithSkinTones(baseEmoji: .brideWithVeil, skinTones: [.dark]), + "👼🏿": EmojiWithSkinTones(baseEmoji: .angel, skinTones: [.dark]), + "💆🏿": EmojiWithSkinTones(baseEmoji: .massage, skinTones: [.dark]), + "💇🏿": EmojiWithSkinTones(baseEmoji: .haircut, skinTones: [.dark]), + "💃🏿": EmojiWithSkinTones(baseEmoji: .dancer, skinTones: [.dark]), + "👭🏿": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.dark]), + "👫🏿": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.dark]), + "👬🏿": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.dark]), + "💏🏿": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.dark]), + "💑🏿": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2563(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🖐🏻": EmojiWithSkinTones(baseEmoji: .raisedHandWithFingersSplayed, skinTones: [.light]), + "🕵🏻": EmojiWithSkinTones(baseEmoji: .sleuthOrSpy, skinTones: [.light]), + "🕺🏻": EmojiWithSkinTones(baseEmoji: .manDancing, skinTones: [.light]), + "🕴🏻": EmojiWithSkinTones(baseEmoji: .manInBusinessSuitLevitating, skinTones: [.light]), + "🖐🏼": EmojiWithSkinTones(baseEmoji: .raisedHandWithFingersSplayed, skinTones: [.mediumLight]), + "🕵🏼": EmojiWithSkinTones(baseEmoji: .sleuthOrSpy, skinTones: [.mediumLight]), + "🕺🏼": EmojiWithSkinTones(baseEmoji: .manDancing, skinTones: [.mediumLight]), + "🕴🏼": EmojiWithSkinTones(baseEmoji: .manInBusinessSuitLevitating, skinTones: [.mediumLight]), + "🖐🏽": EmojiWithSkinTones(baseEmoji: .raisedHandWithFingersSplayed, skinTones: [.medium]), + "🕵🏽": EmojiWithSkinTones(baseEmoji: .sleuthOrSpy, skinTones: [.medium]), + "🕺🏽": EmojiWithSkinTones(baseEmoji: .manDancing, skinTones: [.medium]), + "🕴🏽": EmojiWithSkinTones(baseEmoji: .manInBusinessSuitLevitating, skinTones: [.medium]), + "🖐🏾": EmojiWithSkinTones(baseEmoji: .raisedHandWithFingersSplayed, skinTones: [.mediumDark]), + "🕵🏾": EmojiWithSkinTones(baseEmoji: .sleuthOrSpy, skinTones: [.mediumDark]), + "🕺🏾": EmojiWithSkinTones(baseEmoji: .manDancing, skinTones: [.mediumDark]), + "🕴🏾": EmojiWithSkinTones(baseEmoji: .manInBusinessSuitLevitating, skinTones: [.mediumDark]), + "🖐🏿": EmojiWithSkinTones(baseEmoji: .raisedHandWithFingersSplayed, skinTones: [.dark]), + "🕵🏿": EmojiWithSkinTones(baseEmoji: .sleuthOrSpy, skinTones: [.dark]), + "🕺🏿": EmojiWithSkinTones(baseEmoji: .manDancing, skinTones: [.dark]), + "🕴🏿": EmojiWithSkinTones(baseEmoji: .manInBusinessSuitLevitating, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2564(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🖖🏻": EmojiWithSkinTones(baseEmoji: .spockHand, skinTones: [.light]), + "🖕🏻": EmojiWithSkinTones(baseEmoji: .middleFinger, skinTones: [.light]), + "🖖🏼": EmojiWithSkinTones(baseEmoji: .spockHand, skinTones: [.mediumLight]), + "🖕🏼": EmojiWithSkinTones(baseEmoji: .middleFinger, skinTones: [.mediumLight]), + "🖖🏽": EmojiWithSkinTones(baseEmoji: .spockHand, skinTones: [.medium]), + "🖕🏽": EmojiWithSkinTones(baseEmoji: .middleFinger, skinTones: [.medium]), + "🖖🏾": EmojiWithSkinTones(baseEmoji: .spockHand, skinTones: [.mediumDark]), + "🖕🏾": EmojiWithSkinTones(baseEmoji: .middleFinger, skinTones: [.mediumDark]), + "🖖🏿": EmojiWithSkinTones(baseEmoji: .spockHand, skinTones: [.dark]), + "🖕🏿": EmojiWithSkinTones(baseEmoji: .middleFinger, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2565(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🙌🏻": EmojiWithSkinTones(baseEmoji: .raisedHands, skinTones: [.light]), + "🙏🏻": EmojiWithSkinTones(baseEmoji: .pray, skinTones: [.light]), + "🙍🏻": EmojiWithSkinTones(baseEmoji: .personFrowning, skinTones: [.light]), + "🙎🏻": EmojiWithSkinTones(baseEmoji: .personWithPoutingFace, skinTones: [.light]), + "🙅🏻": EmojiWithSkinTones(baseEmoji: .noGood, skinTones: [.light]), + "🙆🏻": EmojiWithSkinTones(baseEmoji: .okWoman, skinTones: [.light]), + "🙋🏻": EmojiWithSkinTones(baseEmoji: .raisingHand, skinTones: [.light]), + "🙇🏻": EmojiWithSkinTones(baseEmoji: .bow, skinTones: [.light]), + "🙌🏼": EmojiWithSkinTones(baseEmoji: .raisedHands, skinTones: [.mediumLight]), + "🙏🏼": EmojiWithSkinTones(baseEmoji: .pray, skinTones: [.mediumLight]), + "🙍🏼": EmojiWithSkinTones(baseEmoji: .personFrowning, skinTones: [.mediumLight]), + "🙎🏼": EmojiWithSkinTones(baseEmoji: .personWithPoutingFace, skinTones: [.mediumLight]), + "🙅🏼": EmojiWithSkinTones(baseEmoji: .noGood, skinTones: [.mediumLight]), + "🙆🏼": EmojiWithSkinTones(baseEmoji: .okWoman, skinTones: [.mediumLight]), + "🙋🏼": EmojiWithSkinTones(baseEmoji: .raisingHand, skinTones: [.mediumLight]), + "🙇🏼": EmojiWithSkinTones(baseEmoji: .bow, skinTones: [.mediumLight]), + "🙌🏽": EmojiWithSkinTones(baseEmoji: .raisedHands, skinTones: [.medium]), + "🙏🏽": EmojiWithSkinTones(baseEmoji: .pray, skinTones: [.medium]), + "🙍🏽": EmojiWithSkinTones(baseEmoji: .personFrowning, skinTones: [.medium]), + "🙎🏽": EmojiWithSkinTones(baseEmoji: .personWithPoutingFace, skinTones: [.medium]), + "🙅🏽": EmojiWithSkinTones(baseEmoji: .noGood, skinTones: [.medium]), + "🙆🏽": EmojiWithSkinTones(baseEmoji: .okWoman, skinTones: [.medium]), + "🙋🏽": EmojiWithSkinTones(baseEmoji: .raisingHand, skinTones: [.medium]), + "🙇🏽": EmojiWithSkinTones(baseEmoji: .bow, skinTones: [.medium]), + "🙌🏾": EmojiWithSkinTones(baseEmoji: .raisedHands, skinTones: [.mediumDark]), + "🙏🏾": EmojiWithSkinTones(baseEmoji: .pray, skinTones: [.mediumDark]), + "🙍🏾": EmojiWithSkinTones(baseEmoji: .personFrowning, skinTones: [.mediumDark]), + "🙎🏾": EmojiWithSkinTones(baseEmoji: .personWithPoutingFace, skinTones: [.mediumDark]), + "🙅🏾": EmojiWithSkinTones(baseEmoji: .noGood, skinTones: [.mediumDark]), + "🙆🏾": EmojiWithSkinTones(baseEmoji: .okWoman, skinTones: [.mediumDark]), + "🙋🏾": EmojiWithSkinTones(baseEmoji: .raisingHand, skinTones: [.mediumDark]), + "🙇🏾": EmojiWithSkinTones(baseEmoji: .bow, skinTones: [.mediumDark]), + "🙌🏿": EmojiWithSkinTones(baseEmoji: .raisedHands, skinTones: [.dark]), + "🙏🏿": EmojiWithSkinTones(baseEmoji: .pray, skinTones: [.dark]), + "🙍🏿": EmojiWithSkinTones(baseEmoji: .personFrowning, skinTones: [.dark]), + "🙎🏿": EmojiWithSkinTones(baseEmoji: .personWithPoutingFace, skinTones: [.dark]), + "🙅🏿": EmojiWithSkinTones(baseEmoji: .noGood, skinTones: [.dark]), + "🙆🏿": EmojiWithSkinTones(baseEmoji: .okWoman, skinTones: [.dark]), + "🙋🏿": EmojiWithSkinTones(baseEmoji: .raisingHand, skinTones: [.dark]), + "🙇🏿": EmojiWithSkinTones(baseEmoji: .bow, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2566(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🚶🏻": EmojiWithSkinTones(baseEmoji: .walking, skinTones: [.light]), + "🚣🏻": EmojiWithSkinTones(baseEmoji: .rowboat, skinTones: [.light]), + "🚴🏻": EmojiWithSkinTones(baseEmoji: .bicyclist, skinTones: [.light]), + "🚵🏻": EmojiWithSkinTones(baseEmoji: .mountainBicyclist, skinTones: [.light]), + "🛀🏻": EmojiWithSkinTones(baseEmoji: .bath, skinTones: [.light]), + "🚶🏼": EmojiWithSkinTones(baseEmoji: .walking, skinTones: [.mediumLight]), + "🚣🏼": EmojiWithSkinTones(baseEmoji: .rowboat, skinTones: [.mediumLight]), + "🚴🏼": EmojiWithSkinTones(baseEmoji: .bicyclist, skinTones: [.mediumLight]), + "🚵🏼": EmojiWithSkinTones(baseEmoji: .mountainBicyclist, skinTones: [.mediumLight]), + "🚶🏽": EmojiWithSkinTones(baseEmoji: .walking, skinTones: [.medium]), + "🚣🏽": EmojiWithSkinTones(baseEmoji: .rowboat, skinTones: [.medium]), + "🚴🏽": EmojiWithSkinTones(baseEmoji: .bicyclist, skinTones: [.medium]), + "🚵🏽": EmojiWithSkinTones(baseEmoji: .mountainBicyclist, skinTones: [.medium]), + "🚶🏾": EmojiWithSkinTones(baseEmoji: .walking, skinTones: [.mediumDark]), + "🚣🏾": EmojiWithSkinTones(baseEmoji: .rowboat, skinTones: [.mediumDark]), + "🚴🏾": EmojiWithSkinTones(baseEmoji: .bicyclist, skinTones: [.mediumDark]), + "🚵🏾": EmojiWithSkinTones(baseEmoji: .mountainBicyclist, skinTones: [.mediumDark]), + "🚶🏿": EmojiWithSkinTones(baseEmoji: .walking, skinTones: [.dark]), + "🚣🏿": EmojiWithSkinTones(baseEmoji: .rowboat, skinTones: [.dark]), + "🚴🏿": EmojiWithSkinTones(baseEmoji: .bicyclist, skinTones: [.dark]), + "🚵🏿": EmojiWithSkinTones(baseEmoji: .mountainBicyclist, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2567(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🛌🏻": EmojiWithSkinTones(baseEmoji: .sleepingAccommodation, skinTones: [.light]), + "🛀🏼": EmojiWithSkinTones(baseEmoji: .bath, skinTones: [.mediumLight]), + "🛌🏼": EmojiWithSkinTones(baseEmoji: .sleepingAccommodation, skinTones: [.mediumLight]), + "🛀🏽": EmojiWithSkinTones(baseEmoji: .bath, skinTones: [.medium]), + "🛌🏽": EmojiWithSkinTones(baseEmoji: .sleepingAccommodation, skinTones: [.medium]), + "🛀🏾": EmojiWithSkinTones(baseEmoji: .bath, skinTones: [.mediumDark]), + "🛌🏾": EmojiWithSkinTones(baseEmoji: .sleepingAccommodation, skinTones: [.mediumDark]), + "🛀🏿": EmojiWithSkinTones(baseEmoji: .bath, skinTones: [.dark]), + "🛌🏿": EmojiWithSkinTones(baseEmoji: .sleepingAccommodation, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2572(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🤌🏻": EmojiWithSkinTones(baseEmoji: .pinchedFingers, skinTones: [.light]), + "🤏🏻": EmojiWithSkinTones(baseEmoji: .pinchingHand, skinTones: [.light]), + "🤘🏻": EmojiWithSkinTones(baseEmoji: .theHorns, skinTones: [.light]), + "🤌🏼": EmojiWithSkinTones(baseEmoji: .pinchedFingers, skinTones: [.mediumLight]), + "🤏🏼": EmojiWithSkinTones(baseEmoji: .pinchingHand, skinTones: [.mediumLight]), + "🤌🏽": EmojiWithSkinTones(baseEmoji: .pinchedFingers, skinTones: [.medium]), + "🤏🏽": EmojiWithSkinTones(baseEmoji: .pinchingHand, skinTones: [.medium]), + "🤌🏾": EmojiWithSkinTones(baseEmoji: .pinchedFingers, skinTones: [.mediumDark]), + "🤏🏾": EmojiWithSkinTones(baseEmoji: .pinchingHand, skinTones: [.mediumDark]), + "🤌🏿": EmojiWithSkinTones(baseEmoji: .pinchedFingers, skinTones: [.dark]), + "🤏🏿": EmojiWithSkinTones(baseEmoji: .pinchingHand, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2573(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🤚🏻": EmojiWithSkinTones(baseEmoji: .raisedBackOfHand, skinTones: [.light]), + "🤞🏻": EmojiWithSkinTones(baseEmoji: .crossedFingers, skinTones: [.light]), + "🤟🏻": EmojiWithSkinTones(baseEmoji: .iLoveYouHandSign, skinTones: [.light]), + "🤙🏻": EmojiWithSkinTones(baseEmoji: .callMeHand, skinTones: [.light]), + "🤛🏻": EmojiWithSkinTones(baseEmoji: .leftFacingFist, skinTones: [.light]), + "🤜🏻": EmojiWithSkinTones(baseEmoji: .rightFacingFist, skinTones: [.light]), + "🤲🏻": EmojiWithSkinTones(baseEmoji: .palmsUpTogether, skinTones: [.light]), + "🤝🏻": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.light]), + "🤳🏻": EmojiWithSkinTones(baseEmoji: .selfie, skinTones: [.light]), + "🤦🏻": EmojiWithSkinTones(baseEmoji: .facePalm, skinTones: [.light]), + "🤷🏻": EmojiWithSkinTones(baseEmoji: .shrug, skinTones: [.light]), + "🥷🏻": EmojiWithSkinTones(baseEmoji: .ninja, skinTones: [.light]), + "🤴🏻": EmojiWithSkinTones(baseEmoji: .prince, skinTones: [.light]), + "🤵🏻": EmojiWithSkinTones(baseEmoji: .personInTuxedo, skinTones: [.light]), + "🤰🏻": EmojiWithSkinTones(baseEmoji: .pregnantWoman, skinTones: [.light]), + "🤱🏻": EmojiWithSkinTones(baseEmoji: .breastFeeding, skinTones: [.light]), + "🤶🏻": EmojiWithSkinTones(baseEmoji: .mrsClaus, skinTones: [.light]), + "🤸🏻": EmojiWithSkinTones(baseEmoji: .personDoingCartwheel, skinTones: [.light]), + "🤽🏻": EmojiWithSkinTones(baseEmoji: .waterPolo, skinTones: [.light]), + "🤾🏻": EmojiWithSkinTones(baseEmoji: .handball, skinTones: [.light]), + "🤹🏻": EmojiWithSkinTones(baseEmoji: .juggling, skinTones: [.light]), + "🤚🏼": EmojiWithSkinTones(baseEmoji: .raisedBackOfHand, skinTones: [.mediumLight]), + "🤞🏼": EmojiWithSkinTones(baseEmoji: .crossedFingers, skinTones: [.mediumLight]), + "🤟🏼": EmojiWithSkinTones(baseEmoji: .iLoveYouHandSign, skinTones: [.mediumLight]), + "🤘🏼": EmojiWithSkinTones(baseEmoji: .theHorns, skinTones: [.mediumLight]), + "🤙🏼": EmojiWithSkinTones(baseEmoji: .callMeHand, skinTones: [.mediumLight]), + "🤛🏼": EmojiWithSkinTones(baseEmoji: .leftFacingFist, skinTones: [.mediumLight]), + "🤜🏼": EmojiWithSkinTones(baseEmoji: .rightFacingFist, skinTones: [.mediumLight]), + "🤲🏼": EmojiWithSkinTones(baseEmoji: .palmsUpTogether, skinTones: [.mediumLight]), + "🤝🏼": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.mediumLight]), + "🤳🏼": EmojiWithSkinTones(baseEmoji: .selfie, skinTones: [.mediumLight]), + "🤦🏼": EmojiWithSkinTones(baseEmoji: .facePalm, skinTones: [.mediumLight]), + "🤷🏼": EmojiWithSkinTones(baseEmoji: .shrug, skinTones: [.mediumLight]), + "🥷🏼": EmojiWithSkinTones(baseEmoji: .ninja, skinTones: [.mediumLight]), + "🤴🏼": EmojiWithSkinTones(baseEmoji: .prince, skinTones: [.mediumLight]), + "🤵🏼": EmojiWithSkinTones(baseEmoji: .personInTuxedo, skinTones: [.mediumLight]), + "🤰🏼": EmojiWithSkinTones(baseEmoji: .pregnantWoman, skinTones: [.mediumLight]), + "🤱🏼": EmojiWithSkinTones(baseEmoji: .breastFeeding, skinTones: [.mediumLight]), + "🤶🏼": EmojiWithSkinTones(baseEmoji: .mrsClaus, skinTones: [.mediumLight]), + "🤸🏼": EmojiWithSkinTones(baseEmoji: .personDoingCartwheel, skinTones: [.mediumLight]), + "🤽🏼": EmojiWithSkinTones(baseEmoji: .waterPolo, skinTones: [.mediumLight]), + "🤾🏼": EmojiWithSkinTones(baseEmoji: .handball, skinTones: [.mediumLight]), + "🤹🏼": EmojiWithSkinTones(baseEmoji: .juggling, skinTones: [.mediumLight]), + "🤚🏽": EmojiWithSkinTones(baseEmoji: .raisedBackOfHand, skinTones: [.medium]), + "🤞🏽": EmojiWithSkinTones(baseEmoji: .crossedFingers, skinTones: [.medium]), + "🤟🏽": EmojiWithSkinTones(baseEmoji: .iLoveYouHandSign, skinTones: [.medium]), + "🤘🏽": EmojiWithSkinTones(baseEmoji: .theHorns, skinTones: [.medium]), + "🤙🏽": EmojiWithSkinTones(baseEmoji: .callMeHand, skinTones: [.medium]), + "🤛🏽": EmojiWithSkinTones(baseEmoji: .leftFacingFist, skinTones: [.medium]), + "🤜🏽": EmojiWithSkinTones(baseEmoji: .rightFacingFist, skinTones: [.medium]), + "🤲🏽": EmojiWithSkinTones(baseEmoji: .palmsUpTogether, skinTones: [.medium]), + "🤝🏽": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.medium]), + "🤳🏽": EmojiWithSkinTones(baseEmoji: .selfie, skinTones: [.medium]), + "🤦🏽": EmojiWithSkinTones(baseEmoji: .facePalm, skinTones: [.medium]), + "🤷🏽": EmojiWithSkinTones(baseEmoji: .shrug, skinTones: [.medium]), + "🥷🏽": EmojiWithSkinTones(baseEmoji: .ninja, skinTones: [.medium]), + "🤴🏽": EmojiWithSkinTones(baseEmoji: .prince, skinTones: [.medium]), + "🤵🏽": EmojiWithSkinTones(baseEmoji: .personInTuxedo, skinTones: [.medium]), + "🤰🏽": EmojiWithSkinTones(baseEmoji: .pregnantWoman, skinTones: [.medium]), + "🤱🏽": EmojiWithSkinTones(baseEmoji: .breastFeeding, skinTones: [.medium]), + "🤶🏽": EmojiWithSkinTones(baseEmoji: .mrsClaus, skinTones: [.medium]), + "🤸🏽": EmojiWithSkinTones(baseEmoji: .personDoingCartwheel, skinTones: [.medium]), + "🤽🏽": EmojiWithSkinTones(baseEmoji: .waterPolo, skinTones: [.medium]), + "🤾🏽": EmojiWithSkinTones(baseEmoji: .handball, skinTones: [.medium]), + "🤹🏽": EmojiWithSkinTones(baseEmoji: .juggling, skinTones: [.medium]), + "🤚🏾": EmojiWithSkinTones(baseEmoji: .raisedBackOfHand, skinTones: [.mediumDark]), + "🤞🏾": EmojiWithSkinTones(baseEmoji: .crossedFingers, skinTones: [.mediumDark]), + "🤟🏾": EmojiWithSkinTones(baseEmoji: .iLoveYouHandSign, skinTones: [.mediumDark]), + "🤘🏾": EmojiWithSkinTones(baseEmoji: .theHorns, skinTones: [.mediumDark]), + "🤙🏾": EmojiWithSkinTones(baseEmoji: .callMeHand, skinTones: [.mediumDark]), + "🤛🏾": EmojiWithSkinTones(baseEmoji: .leftFacingFist, skinTones: [.mediumDark]), + "🤜🏾": EmojiWithSkinTones(baseEmoji: .rightFacingFist, skinTones: [.mediumDark]), + "🤲🏾": EmojiWithSkinTones(baseEmoji: .palmsUpTogether, skinTones: [.mediumDark]), + "🤝🏾": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.mediumDark]), + "🤳🏾": EmojiWithSkinTones(baseEmoji: .selfie, skinTones: [.mediumDark]), + "🤦🏾": EmojiWithSkinTones(baseEmoji: .facePalm, skinTones: [.mediumDark]), + "🤷🏾": EmojiWithSkinTones(baseEmoji: .shrug, skinTones: [.mediumDark]), + "🥷🏾": EmojiWithSkinTones(baseEmoji: .ninja, skinTones: [.mediumDark]), + "🤴🏾": EmojiWithSkinTones(baseEmoji: .prince, skinTones: [.mediumDark]), + "🤵🏾": EmojiWithSkinTones(baseEmoji: .personInTuxedo, skinTones: [.mediumDark]), + "🤰🏾": EmojiWithSkinTones(baseEmoji: .pregnantWoman, skinTones: [.mediumDark]), + "🤱🏾": EmojiWithSkinTones(baseEmoji: .breastFeeding, skinTones: [.mediumDark]), + "🤶🏾": EmojiWithSkinTones(baseEmoji: .mrsClaus, skinTones: [.mediumDark]), + "🤸🏾": EmojiWithSkinTones(baseEmoji: .personDoingCartwheel, skinTones: [.mediumDark]), + "🤽🏾": EmojiWithSkinTones(baseEmoji: .waterPolo, skinTones: [.mediumDark]), + "🤾🏾": EmojiWithSkinTones(baseEmoji: .handball, skinTones: [.mediumDark]), + "🤹🏾": EmojiWithSkinTones(baseEmoji: .juggling, skinTones: [.mediumDark]), + "🤚🏿": EmojiWithSkinTones(baseEmoji: .raisedBackOfHand, skinTones: [.dark]), + "🤞🏿": EmojiWithSkinTones(baseEmoji: .crossedFingers, skinTones: [.dark]), + "🤟🏿": EmojiWithSkinTones(baseEmoji: .iLoveYouHandSign, skinTones: [.dark]), + "🤘🏿": EmojiWithSkinTones(baseEmoji: .theHorns, skinTones: [.dark]), + "🤙🏿": EmojiWithSkinTones(baseEmoji: .callMeHand, skinTones: [.dark]), + "🤛🏿": EmojiWithSkinTones(baseEmoji: .leftFacingFist, skinTones: [.dark]), + "🤜🏿": EmojiWithSkinTones(baseEmoji: .rightFacingFist, skinTones: [.dark]), + "🤲🏿": EmojiWithSkinTones(baseEmoji: .palmsUpTogether, skinTones: [.dark]), + "🤝🏿": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.dark]), + "🤳🏿": EmojiWithSkinTones(baseEmoji: .selfie, skinTones: [.dark]), + "🤦🏿": EmojiWithSkinTones(baseEmoji: .facePalm, skinTones: [.dark]), + "🤷🏿": EmojiWithSkinTones(baseEmoji: .shrug, skinTones: [.dark]), + "🥷🏿": EmojiWithSkinTones(baseEmoji: .ninja, skinTones: [.dark]), + "🤴🏿": EmojiWithSkinTones(baseEmoji: .prince, skinTones: [.dark]), + "🤵🏿": EmojiWithSkinTones(baseEmoji: .personInTuxedo, skinTones: [.dark]), + "🤰🏿": EmojiWithSkinTones(baseEmoji: .pregnantWoman, skinTones: [.dark]), + "🤱🏿": EmojiWithSkinTones(baseEmoji: .breastFeeding, skinTones: [.dark]), + "🤶🏿": EmojiWithSkinTones(baseEmoji: .mrsClaus, skinTones: [.dark]), + "🤸🏿": EmojiWithSkinTones(baseEmoji: .personDoingCartwheel, skinTones: [.dark]), + "🤽🏿": EmojiWithSkinTones(baseEmoji: .waterPolo, skinTones: [.dark]), + "🤾🏿": EmojiWithSkinTones(baseEmoji: .handball, skinTones: [.dark]), + "🤹🏿": EmojiWithSkinTones(baseEmoji: .juggling, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2574(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🦵🏻": EmojiWithSkinTones(baseEmoji: .leg, skinTones: [.light]), + "🦶🏻": EmojiWithSkinTones(baseEmoji: .foot, skinTones: [.light]), + "🦻🏻": EmojiWithSkinTones(baseEmoji: .earWithHearingAid, skinTones: [.light]), + "🧒🏻": EmojiWithSkinTones(baseEmoji: .child, skinTones: [.light]), + "🧑🏻": EmojiWithSkinTones(baseEmoji: .adult, skinTones: [.light]), + "🧔🏻": EmojiWithSkinTones(baseEmoji: .beardedPerson, skinTones: [.light]), + "🧓🏻": EmojiWithSkinTones(baseEmoji: .olderAdult, skinTones: [.light]), + "🧏🏻": EmojiWithSkinTones(baseEmoji: .deafPerson, skinTones: [.light]), + "🧕🏻": EmojiWithSkinTones(baseEmoji: .personWithHeadscarf, skinTones: [.light]), + "🦸🏻": EmojiWithSkinTones(baseEmoji: .superhero, skinTones: [.light]), + "🦹🏻": EmojiWithSkinTones(baseEmoji: .supervillain, skinTones: [.light]), + "🧙🏻": EmojiWithSkinTones(baseEmoji: .mage, skinTones: [.light]), + "🧚🏻": EmojiWithSkinTones(baseEmoji: .fairy, skinTones: [.light]), + "🧛🏻": EmojiWithSkinTones(baseEmoji: .vampire, skinTones: [.light]), + "🧜🏻": EmojiWithSkinTones(baseEmoji: .merperson, skinTones: [.light]), + "🧝🏻": EmojiWithSkinTones(baseEmoji: .elf, skinTones: [.light]), + "🧍🏻": EmojiWithSkinTones(baseEmoji: .standingPerson, skinTones: [.light]), + "🧎🏻": EmojiWithSkinTones(baseEmoji: .kneelingPerson, skinTones: [.light]), + "🧖🏻": EmojiWithSkinTones(baseEmoji: .personInSteamyRoom, skinTones: [.light]), + "🧗🏻": EmojiWithSkinTones(baseEmoji: .personClimbing, skinTones: [.light]), + "🧘🏻": EmojiWithSkinTones(baseEmoji: .personInLotusPosition, skinTones: [.light]), + "🦵🏼": EmojiWithSkinTones(baseEmoji: .leg, skinTones: [.mediumLight]), + "🦶🏼": EmojiWithSkinTones(baseEmoji: .foot, skinTones: [.mediumLight]), + "🦻🏼": EmojiWithSkinTones(baseEmoji: .earWithHearingAid, skinTones: [.mediumLight]), + "🧒🏼": EmojiWithSkinTones(baseEmoji: .child, skinTones: [.mediumLight]), + "🧑🏼": EmojiWithSkinTones(baseEmoji: .adult, skinTones: [.mediumLight]), + "🧔🏼": EmojiWithSkinTones(baseEmoji: .beardedPerson, skinTones: [.mediumLight]), + "🧓🏼": EmojiWithSkinTones(baseEmoji: .olderAdult, skinTones: [.mediumLight]), + "🧏🏼": EmojiWithSkinTones(baseEmoji: .deafPerson, skinTones: [.mediumLight]), + "🧕🏼": EmojiWithSkinTones(baseEmoji: .personWithHeadscarf, skinTones: [.mediumLight]), + "🦸🏼": EmojiWithSkinTones(baseEmoji: .superhero, skinTones: [.mediumLight]), + "🦹🏼": EmojiWithSkinTones(baseEmoji: .supervillain, skinTones: [.mediumLight]), + "🧙🏼": EmojiWithSkinTones(baseEmoji: .mage, skinTones: [.mediumLight]), + "🧚🏼": EmojiWithSkinTones(baseEmoji: .fairy, skinTones: [.mediumLight]), + "🧛🏼": EmojiWithSkinTones(baseEmoji: .vampire, skinTones: [.mediumLight]), + "🧜🏼": EmojiWithSkinTones(baseEmoji: .merperson, skinTones: [.mediumLight]), + "🧝🏼": EmojiWithSkinTones(baseEmoji: .elf, skinTones: [.mediumLight]), + "🧍🏼": EmojiWithSkinTones(baseEmoji: .standingPerson, skinTones: [.mediumLight]), + "🧎🏼": EmojiWithSkinTones(baseEmoji: .kneelingPerson, skinTones: [.mediumLight]), + "🧖🏼": EmojiWithSkinTones(baseEmoji: .personInSteamyRoom, skinTones: [.mediumLight]), + "🧗🏼": EmojiWithSkinTones(baseEmoji: .personClimbing, skinTones: [.mediumLight]), + "🧘🏼": EmojiWithSkinTones(baseEmoji: .personInLotusPosition, skinTones: [.mediumLight]), + "🦵🏽": EmojiWithSkinTones(baseEmoji: .leg, skinTones: [.medium]), + "🦶🏽": EmojiWithSkinTones(baseEmoji: .foot, skinTones: [.medium]), + "🦻🏽": EmojiWithSkinTones(baseEmoji: .earWithHearingAid, skinTones: [.medium]), + "🧒🏽": EmojiWithSkinTones(baseEmoji: .child, skinTones: [.medium]), + "🧑🏽": EmojiWithSkinTones(baseEmoji: .adult, skinTones: [.medium]), + "🧔🏽": EmojiWithSkinTones(baseEmoji: .beardedPerson, skinTones: [.medium]), + "🧓🏽": EmojiWithSkinTones(baseEmoji: .olderAdult, skinTones: [.medium]), + "🧏🏽": EmojiWithSkinTones(baseEmoji: .deafPerson, skinTones: [.medium]), + "🧕🏽": EmojiWithSkinTones(baseEmoji: .personWithHeadscarf, skinTones: [.medium]), + "🦸🏽": EmojiWithSkinTones(baseEmoji: .superhero, skinTones: [.medium]), + "🦹🏽": EmojiWithSkinTones(baseEmoji: .supervillain, skinTones: [.medium]), + "🧙🏽": EmojiWithSkinTones(baseEmoji: .mage, skinTones: [.medium]), + "🧚🏽": EmojiWithSkinTones(baseEmoji: .fairy, skinTones: [.medium]), + "🧛🏽": EmojiWithSkinTones(baseEmoji: .vampire, skinTones: [.medium]), + "🧜🏽": EmojiWithSkinTones(baseEmoji: .merperson, skinTones: [.medium]), + "🧝🏽": EmojiWithSkinTones(baseEmoji: .elf, skinTones: [.medium]), + "🧍🏽": EmojiWithSkinTones(baseEmoji: .standingPerson, skinTones: [.medium]), + "🧎🏽": EmojiWithSkinTones(baseEmoji: .kneelingPerson, skinTones: [.medium]), + "🧖🏽": EmojiWithSkinTones(baseEmoji: .personInSteamyRoom, skinTones: [.medium]), + "🧗🏽": EmojiWithSkinTones(baseEmoji: .personClimbing, skinTones: [.medium]), + "🧘🏽": EmojiWithSkinTones(baseEmoji: .personInLotusPosition, skinTones: [.medium]), + "🦵🏾": EmojiWithSkinTones(baseEmoji: .leg, skinTones: [.mediumDark]), + "🦶🏾": EmojiWithSkinTones(baseEmoji: .foot, skinTones: [.mediumDark]), + "🦻🏾": EmojiWithSkinTones(baseEmoji: .earWithHearingAid, skinTones: [.mediumDark]), + "🧒🏾": EmojiWithSkinTones(baseEmoji: .child, skinTones: [.mediumDark]), + "🧑🏾": EmojiWithSkinTones(baseEmoji: .adult, skinTones: [.mediumDark]), + "🧔🏾": EmojiWithSkinTones(baseEmoji: .beardedPerson, skinTones: [.mediumDark]), + "🧓🏾": EmojiWithSkinTones(baseEmoji: .olderAdult, skinTones: [.mediumDark]), + "🧏🏾": EmojiWithSkinTones(baseEmoji: .deafPerson, skinTones: [.mediumDark]), + "🧕🏾": EmojiWithSkinTones(baseEmoji: .personWithHeadscarf, skinTones: [.mediumDark]), + "🦸🏾": EmojiWithSkinTones(baseEmoji: .superhero, skinTones: [.mediumDark]), + "🦹🏾": EmojiWithSkinTones(baseEmoji: .supervillain, skinTones: [.mediumDark]), + "🧙🏾": EmojiWithSkinTones(baseEmoji: .mage, skinTones: [.mediumDark]), + "🧚🏾": EmojiWithSkinTones(baseEmoji: .fairy, skinTones: [.mediumDark]), + "🧛🏾": EmojiWithSkinTones(baseEmoji: .vampire, skinTones: [.mediumDark]), + "🧜🏾": EmojiWithSkinTones(baseEmoji: .merperson, skinTones: [.mediumDark]), + "🧝🏾": EmojiWithSkinTones(baseEmoji: .elf, skinTones: [.mediumDark]), + "🧍🏾": EmojiWithSkinTones(baseEmoji: .standingPerson, skinTones: [.mediumDark]), + "🧎🏾": EmojiWithSkinTones(baseEmoji: .kneelingPerson, skinTones: [.mediumDark]), + "🧖🏾": EmojiWithSkinTones(baseEmoji: .personInSteamyRoom, skinTones: [.mediumDark]), + "🧗🏾": EmojiWithSkinTones(baseEmoji: .personClimbing, skinTones: [.mediumDark]), + "🧘🏾": EmojiWithSkinTones(baseEmoji: .personInLotusPosition, skinTones: [.mediumDark]), + "🦵🏿": EmojiWithSkinTones(baseEmoji: .leg, skinTones: [.dark]), + "🦶🏿": EmojiWithSkinTones(baseEmoji: .foot, skinTones: [.dark]), + "🦻🏿": EmojiWithSkinTones(baseEmoji: .earWithHearingAid, skinTones: [.dark]), + "🧒🏿": EmojiWithSkinTones(baseEmoji: .child, skinTones: [.dark]), + "🧑🏿": EmojiWithSkinTones(baseEmoji: .adult, skinTones: [.dark]), + "🧔🏿": EmojiWithSkinTones(baseEmoji: .beardedPerson, skinTones: [.dark]), + "🧓🏿": EmojiWithSkinTones(baseEmoji: .olderAdult, skinTones: [.dark]), + "🧏🏿": EmojiWithSkinTones(baseEmoji: .deafPerson, skinTones: [.dark]), + "🧕🏿": EmojiWithSkinTones(baseEmoji: .personWithHeadscarf, skinTones: [.dark]), + "🦸🏿": EmojiWithSkinTones(baseEmoji: .superhero, skinTones: [.dark]), + "🦹🏿": EmojiWithSkinTones(baseEmoji: .supervillain, skinTones: [.dark]), + "🧙🏿": EmojiWithSkinTones(baseEmoji: .mage, skinTones: [.dark]), + "🧚🏿": EmojiWithSkinTones(baseEmoji: .fairy, skinTones: [.dark]), + "🧛🏿": EmojiWithSkinTones(baseEmoji: .vampire, skinTones: [.dark]), + "🧜🏿": EmojiWithSkinTones(baseEmoji: .merperson, skinTones: [.dark]), + "🧍🏿": EmojiWithSkinTones(baseEmoji: .standingPerson, skinTones: [.dark]), + "🧎🏿": EmojiWithSkinTones(baseEmoji: .kneelingPerson, skinTones: [.dark]), + "🧖🏿": EmojiWithSkinTones(baseEmoji: .personInSteamyRoom, skinTones: [.dark]), + "🧗🏿": EmojiWithSkinTones(baseEmoji: .personClimbing, skinTones: [.dark]), + "🧘🏿": EmojiWithSkinTones(baseEmoji: .personInLotusPosition, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2575(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧝🏿": EmojiWithSkinTones(baseEmoji: .elf, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2577(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🫱🏻": EmojiWithSkinTones(baseEmoji: .rightwardsHand, skinTones: [.light]), + "🫲🏻": EmojiWithSkinTones(baseEmoji: .leftwardsHand, skinTones: [.light]), + "🫳🏻": EmojiWithSkinTones(baseEmoji: .palmDownHand, skinTones: [.light]), + "🫴🏻": EmojiWithSkinTones(baseEmoji: .palmUpHand, skinTones: [.light]), + "🫷🏻": EmojiWithSkinTones(baseEmoji: .leftwardsPushingHand, skinTones: [.light]), + "🫸🏻": EmojiWithSkinTones(baseEmoji: .rightwardsPushingHand, skinTones: [.light]), + "🫰🏻": EmojiWithSkinTones(baseEmoji: .handWithIndexFingerAndThumbCrossed, skinTones: [.light]), + "🫵🏻": EmojiWithSkinTones(baseEmoji: .indexPointingAtTheViewer, skinTones: [.light]), + "🫶🏻": EmojiWithSkinTones(baseEmoji: .heartHands, skinTones: [.light]), + "🫅🏻": EmojiWithSkinTones(baseEmoji: .personWithCrown, skinTones: [.light]), + "🫃🏻": EmojiWithSkinTones(baseEmoji: .pregnantMan, skinTones: [.light]), + "🫄🏻": EmojiWithSkinTones(baseEmoji: .pregnantPerson, skinTones: [.light]), + "🫱🏼": EmojiWithSkinTones(baseEmoji: .rightwardsHand, skinTones: [.mediumLight]), + "🫲🏼": EmojiWithSkinTones(baseEmoji: .leftwardsHand, skinTones: [.mediumLight]), + "🫳🏼": EmojiWithSkinTones(baseEmoji: .palmDownHand, skinTones: [.mediumLight]), + "🫴🏼": EmojiWithSkinTones(baseEmoji: .palmUpHand, skinTones: [.mediumLight]), + "🫷🏼": EmojiWithSkinTones(baseEmoji: .leftwardsPushingHand, skinTones: [.mediumLight]), + "🫸🏼": EmojiWithSkinTones(baseEmoji: .rightwardsPushingHand, skinTones: [.mediumLight]), + "🫰🏼": EmojiWithSkinTones(baseEmoji: .handWithIndexFingerAndThumbCrossed, skinTones: [.mediumLight]), + "🫵🏼": EmojiWithSkinTones(baseEmoji: .indexPointingAtTheViewer, skinTones: [.mediumLight]), + "🫶🏼": EmojiWithSkinTones(baseEmoji: .heartHands, skinTones: [.mediumLight]), + "🫅🏼": EmojiWithSkinTones(baseEmoji: .personWithCrown, skinTones: [.mediumLight]), + "🫃🏼": EmojiWithSkinTones(baseEmoji: .pregnantMan, skinTones: [.mediumLight]), + "🫄🏼": EmojiWithSkinTones(baseEmoji: .pregnantPerson, skinTones: [.mediumLight]), + "🫱🏽": EmojiWithSkinTones(baseEmoji: .rightwardsHand, skinTones: [.medium]), + "🫲🏽": EmojiWithSkinTones(baseEmoji: .leftwardsHand, skinTones: [.medium]), + "🫳🏽": EmojiWithSkinTones(baseEmoji: .palmDownHand, skinTones: [.medium]), + "🫴🏽": EmojiWithSkinTones(baseEmoji: .palmUpHand, skinTones: [.medium]), + "🫷🏽": EmojiWithSkinTones(baseEmoji: .leftwardsPushingHand, skinTones: [.medium]), + "🫸🏽": EmojiWithSkinTones(baseEmoji: .rightwardsPushingHand, skinTones: [.medium]), + "🫰🏽": EmojiWithSkinTones(baseEmoji: .handWithIndexFingerAndThumbCrossed, skinTones: [.medium]), + "🫵🏽": EmojiWithSkinTones(baseEmoji: .indexPointingAtTheViewer, skinTones: [.medium]), + "🫶🏽": EmojiWithSkinTones(baseEmoji: .heartHands, skinTones: [.medium]), + "🫅🏽": EmojiWithSkinTones(baseEmoji: .personWithCrown, skinTones: [.medium]), + "🫃🏽": EmojiWithSkinTones(baseEmoji: .pregnantMan, skinTones: [.medium]), + "🫄🏽": EmojiWithSkinTones(baseEmoji: .pregnantPerson, skinTones: [.medium]), + "🫱🏾": EmojiWithSkinTones(baseEmoji: .rightwardsHand, skinTones: [.mediumDark]), + "🫲🏾": EmojiWithSkinTones(baseEmoji: .leftwardsHand, skinTones: [.mediumDark]), + "🫳🏾": EmojiWithSkinTones(baseEmoji: .palmDownHand, skinTones: [.mediumDark]), + "🫴🏾": EmojiWithSkinTones(baseEmoji: .palmUpHand, skinTones: [.mediumDark]), + "🫷🏾": EmojiWithSkinTones(baseEmoji: .leftwardsPushingHand, skinTones: [.mediumDark]), + "🫸🏾": EmojiWithSkinTones(baseEmoji: .rightwardsPushingHand, skinTones: [.mediumDark]), + "🫰🏾": EmojiWithSkinTones(baseEmoji: .handWithIndexFingerAndThumbCrossed, skinTones: [.mediumDark]), + "🫵🏾": EmojiWithSkinTones(baseEmoji: .indexPointingAtTheViewer, skinTones: [.mediumDark]), + "🫶🏾": EmojiWithSkinTones(baseEmoji: .heartHands, skinTones: [.mediumDark]), + "🫅🏾": EmojiWithSkinTones(baseEmoji: .personWithCrown, skinTones: [.mediumDark]), + "🫃🏾": EmojiWithSkinTones(baseEmoji: .pregnantMan, skinTones: [.mediumDark]), + "🫄🏾": EmojiWithSkinTones(baseEmoji: .pregnantPerson, skinTones: [.mediumDark]), + "🫱🏿": EmojiWithSkinTones(baseEmoji: .rightwardsHand, skinTones: [.dark]), + "🫲🏿": EmojiWithSkinTones(baseEmoji: .leftwardsHand, skinTones: [.dark]), + "🫳🏿": EmojiWithSkinTones(baseEmoji: .palmDownHand, skinTones: [.dark]), + "🫴🏿": EmojiWithSkinTones(baseEmoji: .palmUpHand, skinTones: [.dark]), + "🫷🏿": EmojiWithSkinTones(baseEmoji: .leftwardsPushingHand, skinTones: [.dark]), + "🫸🏿": EmojiWithSkinTones(baseEmoji: .rightwardsPushingHand, skinTones: [.dark]), + "🫰🏿": EmojiWithSkinTones(baseEmoji: .handWithIndexFingerAndThumbCrossed, skinTones: [.dark]), + "🫵🏿": EmojiWithSkinTones(baseEmoji: .indexPointingAtTheViewer, skinTones: [.dark]), + "🫶🏿": EmojiWithSkinTones(baseEmoji: .heartHands, skinTones: [.dark]), + "🫅🏿": EmojiWithSkinTones(baseEmoji: .personWithCrown, skinTones: [.dark]), + "🫃🏿": EmojiWithSkinTones(baseEmoji: .pregnantMan, skinTones: [.dark]), + "🫄🏿": EmojiWithSkinTones(baseEmoji: .pregnantPerson, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2641(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨‍🌾": EmojiWithSkinTones(baseEmoji: .maleFarmer, skinTones: nil), + "👩‍🌾": EmojiWithSkinTones(baseEmoji: .femaleFarmer, skinTones: nil), + "👨‍🍳": EmojiWithSkinTones(baseEmoji: .maleCook, skinTones: nil), + "👩‍🍳": EmojiWithSkinTones(baseEmoji: .femaleCook, skinTones: nil), + "👩‍🍼": EmojiWithSkinTones(baseEmoji: .womanFeedingBaby, skinTones: nil), + "👨‍🍼": EmojiWithSkinTones(baseEmoji: .manFeedingBaby, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2642(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨‍🎓": EmojiWithSkinTones(baseEmoji: .maleStudent, skinTones: nil), + "👩‍🎓": EmojiWithSkinTones(baseEmoji: .femaleStudent, skinTones: nil), + "👨‍🏫": EmojiWithSkinTones(baseEmoji: .maleTeacher, skinTones: nil), + "👩‍🏫": EmojiWithSkinTones(baseEmoji: .femaleTeacher, skinTones: nil), + "👨‍🏭": EmojiWithSkinTones(baseEmoji: .maleFactoryWorker, skinTones: nil), + "👩‍🏭": EmojiWithSkinTones(baseEmoji: .femaleFactoryWorker, skinTones: nil), + "👨‍🎤": EmojiWithSkinTones(baseEmoji: .maleSinger, skinTones: nil), + "👩‍🎤": EmojiWithSkinTones(baseEmoji: .femaleSinger, skinTones: nil), + "👨‍🎨": EmojiWithSkinTones(baseEmoji: .maleArtist, skinTones: nil), + "👩‍🎨": EmojiWithSkinTones(baseEmoji: .femaleArtist, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2644(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨‍💼": EmojiWithSkinTones(baseEmoji: .maleOfficeWorker, skinTones: nil), + "👩‍💼": EmojiWithSkinTones(baseEmoji: .femaleOfficeWorker, skinTones: nil), + "👨‍💻": EmojiWithSkinTones(baseEmoji: .maleTechnologist, skinTones: nil), + "👩‍💻": EmojiWithSkinTones(baseEmoji: .femaleTechnologist, skinTones: nil), + "👨‍👦": EmojiWithSkinTones(baseEmoji: .manBoy, skinTones: nil), + "👨‍👧": EmojiWithSkinTones(baseEmoji: .manGirl, skinTones: nil), + "👩‍👦": EmojiWithSkinTones(baseEmoji: .womanBoy, skinTones: nil), + "👩‍👧": EmojiWithSkinTones(baseEmoji: .womanGirl, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2646(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨‍🔧": EmojiWithSkinTones(baseEmoji: .maleMechanic, skinTones: nil), + "👩‍🔧": EmojiWithSkinTones(baseEmoji: .femaleMechanic, skinTones: nil), + "👨‍🔬": EmojiWithSkinTones(baseEmoji: .maleScientist, skinTones: nil), + "👩‍🔬": EmojiWithSkinTones(baseEmoji: .femaleScientist, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2649(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "😮‍💨": EmojiWithSkinTones(baseEmoji: .faceExhaling, skinTones: nil), + "😵‍💫": EmojiWithSkinTones(baseEmoji: .faceWithSpiralEyes, skinTones: nil), + "👨‍🚀": EmojiWithSkinTones(baseEmoji: .maleAstronaut, skinTones: nil), + "👩‍🚀": EmojiWithSkinTones(baseEmoji: .femaleAstronaut, skinTones: nil), + "👨‍🚒": EmojiWithSkinTones(baseEmoji: .maleFirefighter, skinTones: nil), + "👩‍🚒": EmojiWithSkinTones(baseEmoji: .femaleFirefighter, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2655(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍🎓": EmojiWithSkinTones(baseEmoji: .student, skinTones: nil), + "🧑‍🌾": EmojiWithSkinTones(baseEmoji: .farmer, skinTones: nil), + "🧑‍🍳": EmojiWithSkinTones(baseEmoji: .cook, skinTones: nil), + "🧑‍🍼": EmojiWithSkinTones(baseEmoji: .personFeedingBaby, skinTones: nil), + "🧑‍🎄": EmojiWithSkinTones(baseEmoji: .mxClaus, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2656(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍🏫": EmojiWithSkinTones(baseEmoji: .teacher, skinTones: nil), + "🧑‍🏭": EmojiWithSkinTones(baseEmoji: .factoryWorker, skinTones: nil), + "🧑‍🎤": EmojiWithSkinTones(baseEmoji: .singer, skinTones: nil), + "🧑‍🎨": EmojiWithSkinTones(baseEmoji: .artist, skinTones: nil), + "🐕‍🦺": EmojiWithSkinTones(baseEmoji: .serviceDog, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2657(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedMan, skinTones: nil), + "👨‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedMan, skinTones: nil), + "👨‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedMan, skinTones: nil), + "👨‍🦲": EmojiWithSkinTones(baseEmoji: .baldMan, skinTones: nil), + "👩‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedWoman, skinTones: nil), + "👩‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedWoman, skinTones: nil), + "👩‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedWoman, skinTones: nil), + "👩‍🦲": EmojiWithSkinTones(baseEmoji: .baldWoman, skinTones: nil), + "👨‍🦯": EmojiWithSkinTones(baseEmoji: .manWithProbingCane, skinTones: nil), + "👩‍🦯": EmojiWithSkinTones(baseEmoji: .womanWithProbingCane, skinTones: nil), + "👨‍🦼": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchair, skinTones: nil), + "👩‍🦼": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchair, skinTones: nil), + "👨‍🦽": EmojiWithSkinTones(baseEmoji: .manInManualWheelchair, skinTones: nil), + "👩‍🦽": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchair, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2658(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍💼": EmojiWithSkinTones(baseEmoji: .officeWorker, skinTones: nil), + "🧑‍💻": EmojiWithSkinTones(baseEmoji: .technologist, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2659(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍🔧": EmojiWithSkinTones(baseEmoji: .mechanic, skinTones: nil), + "🧑‍🔬": EmojiWithSkinTones(baseEmoji: .scientist, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2663(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍🚀": EmojiWithSkinTones(baseEmoji: .astronaut, skinTones: nil), + "🧑‍🚒": EmojiWithSkinTones(baseEmoji: .firefighter, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2671(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedPerson, skinTones: nil), + "🧑‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedPerson, skinTones: nil), + "🧑‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedPerson, skinTones: nil), + "🧑‍🦲": EmojiWithSkinTones(baseEmoji: .baldPerson, skinTones: nil), + "🧑‍🦯": EmojiWithSkinTones(baseEmoji: .personWithProbingCane, skinTones: nil), + "🧑‍🦼": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchair, skinTones: nil), + "🧑‍🦽": EmojiWithSkinTones(baseEmoji: .personInManualWheelchair, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2760(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏌️‍♂️": EmojiWithSkinTones(baseEmoji: .manGolfing, skinTones: nil), + "🏌️‍♀️": EmojiWithSkinTones(baseEmoji: .womanGolfing, skinTones: nil), + "🏋️‍♂️": EmojiWithSkinTones(baseEmoji: .manLiftingWeights, skinTones: nil), + "🏋️‍♀️": EmojiWithSkinTones(baseEmoji: .womanLiftingWeights, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2761(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏳️‍⚧️": EmojiWithSkinTones(baseEmoji: .transgenderFlag, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2764(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🕵️‍♂️": EmojiWithSkinTones(baseEmoji: .maleDetective, skinTones: nil), + "🕵️‍♀️": EmojiWithSkinTones(baseEmoji: .femaleDetective, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3289(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏳️‍🌈": EmojiWithSkinTones(baseEmoji: .rainbowFlag, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3295(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "😶‍🌫️": EmojiWithSkinTones(baseEmoji: .faceInClouds, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3389(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏃🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manRunning, skinTones: [.light]), + "🏃🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanRunning, skinTones: [.light]), + "🏌🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manGolfing, skinTones: [.light]), + "🏌🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanGolfing, skinTones: [.light]), + "🏄🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manSurfing, skinTones: [.light]), + "🏄🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanSurfing, skinTones: [.light]), + "🏊🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manSwimming, skinTones: [.light]), + "🏊🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanSwimming, skinTones: [.light]), + "🏋🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manLiftingWeights, skinTones: [.light]), + "🏋🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanLiftingWeights, skinTones: [.light]), + "🏃🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manRunning, skinTones: [.mediumLight]), + "🏃🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanRunning, skinTones: [.mediumLight]), + "🏌🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manGolfing, skinTones: [.mediumLight]), + "🏌🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanGolfing, skinTones: [.mediumLight]), + "🏄🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manSurfing, skinTones: [.mediumLight]), + "🏄🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanSurfing, skinTones: [.mediumLight]), + "🏊🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manSwimming, skinTones: [.mediumLight]), + "🏊🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanSwimming, skinTones: [.mediumLight]), + "🏋🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manLiftingWeights, skinTones: [.mediumLight]), + "🏋🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanLiftingWeights, skinTones: [.mediumLight]), + "🏃🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manRunning, skinTones: [.medium]), + "🏃🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanRunning, skinTones: [.medium]), + "🏌🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manGolfing, skinTones: [.medium]), + "🏌🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanGolfing, skinTones: [.medium]), + "🏄🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manSurfing, skinTones: [.medium]), + "🏄🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanSurfing, skinTones: [.medium]), + "🏊🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manSwimming, skinTones: [.medium]), + "🏊🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanSwimming, skinTones: [.medium]), + "🏋🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manLiftingWeights, skinTones: [.medium]), + "🏋🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanLiftingWeights, skinTones: [.medium]), + "🏃🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manRunning, skinTones: [.mediumDark]), + "🏃🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanRunning, skinTones: [.mediumDark]), + "🏌🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manGolfing, skinTones: [.mediumDark]), + "🏌🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanGolfing, skinTones: [.mediumDark]), + "🏄🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manSurfing, skinTones: [.mediumDark]), + "🏄🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanSurfing, skinTones: [.mediumDark]), + "🏊🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manSwimming, skinTones: [.mediumDark]), + "🏊🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanSwimming, skinTones: [.mediumDark]), + "🏋🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manLiftingWeights, skinTones: [.mediumDark]), + "🏋🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanLiftingWeights, skinTones: [.mediumDark]), + "🏃🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manRunning, skinTones: [.dark]), + "🏃🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanRunning, skinTones: [.dark]), + "🏌🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manGolfing, skinTones: [.dark]), + "🏌🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanGolfing, skinTones: [.dark]), + "🏄🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manSurfing, skinTones: [.dark]), + "🏄🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanSurfing, skinTones: [.dark]), + "🏊🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manSwimming, skinTones: [.dark]), + "🏊🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanSwimming, skinTones: [.dark]), + "🏋🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manLiftingWeights, skinTones: [.dark]), + "🏋🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanLiftingWeights, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3391(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👱🏻‍♀️": EmojiWithSkinTones(baseEmoji: .blondHairedWoman, skinTones: [.light]), + "👱🏻‍♂️": EmojiWithSkinTones(baseEmoji: .blondHairedMan, skinTones: [.light]), + "💁🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manTippingHand, skinTones: [.light]), + "💁🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanTippingHand, skinTones: [.light]), + "👮🏻‍♂️": EmojiWithSkinTones(baseEmoji: .malePoliceOfficer, skinTones: [.light]), + "👮🏻‍♀️": EmojiWithSkinTones(baseEmoji: .femalePoliceOfficer, skinTones: [.light]), + "💂🏻‍♂️": EmojiWithSkinTones(baseEmoji: .maleGuard, skinTones: [.light]), + "💂🏻‍♀️": EmojiWithSkinTones(baseEmoji: .femaleGuard, skinTones: [.light]), + "👷🏻‍♂️": EmojiWithSkinTones(baseEmoji: .maleConstructionWorker, skinTones: [.light]), + "👷🏻‍♀️": EmojiWithSkinTones(baseEmoji: .femaleConstructionWorker, skinTones: [.light]), + "👳🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manWearingTurban, skinTones: [.light]), + "👳🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanWearingTurban, skinTones: [.light]), + "👰🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manWithVeil, skinTones: [.light]), + "👰🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanWithVeil, skinTones: [.light]), + "💆🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manGettingMassage, skinTones: [.light]), + "💆🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanGettingMassage, skinTones: [.light]), + "💇🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manGettingHaircut, skinTones: [.light]), + "💇🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanGettingHaircut, skinTones: [.light]), + "👱🏼‍♀️": EmojiWithSkinTones(baseEmoji: .blondHairedWoman, skinTones: [.mediumLight]), + "👱🏼‍♂️": EmojiWithSkinTones(baseEmoji: .blondHairedMan, skinTones: [.mediumLight]), + "💁🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manTippingHand, skinTones: [.mediumLight]), + "💁🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanTippingHand, skinTones: [.mediumLight]), + "👮🏼‍♂️": EmojiWithSkinTones(baseEmoji: .malePoliceOfficer, skinTones: [.mediumLight]), + "👮🏼‍♀️": EmojiWithSkinTones(baseEmoji: .femalePoliceOfficer, skinTones: [.mediumLight]), + "💂🏼‍♂️": EmojiWithSkinTones(baseEmoji: .maleGuard, skinTones: [.mediumLight]), + "💂🏼‍♀️": EmojiWithSkinTones(baseEmoji: .femaleGuard, skinTones: [.mediumLight]), + "👷🏼‍♂️": EmojiWithSkinTones(baseEmoji: .maleConstructionWorker, skinTones: [.mediumLight]), + "👷🏼‍♀️": EmojiWithSkinTones(baseEmoji: .femaleConstructionWorker, skinTones: [.mediumLight]), + "👳🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manWearingTurban, skinTones: [.mediumLight]), + "👳🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanWearingTurban, skinTones: [.mediumLight]), + "👰🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manWithVeil, skinTones: [.mediumLight]), + "👰🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanWithVeil, skinTones: [.mediumLight]), + "💆🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manGettingMassage, skinTones: [.mediumLight]), + "💆🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanGettingMassage, skinTones: [.mediumLight]), + "💇🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manGettingHaircut, skinTones: [.mediumLight]), + "💇🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanGettingHaircut, skinTones: [.mediumLight]), + "👱🏽‍♀️": EmojiWithSkinTones(baseEmoji: .blondHairedWoman, skinTones: [.medium]), + "👱🏽‍♂️": EmojiWithSkinTones(baseEmoji: .blondHairedMan, skinTones: [.medium]), + "💁🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manTippingHand, skinTones: [.medium]), + "💁🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanTippingHand, skinTones: [.medium]), + "👮🏽‍♂️": EmojiWithSkinTones(baseEmoji: .malePoliceOfficer, skinTones: [.medium]), + "👮🏽‍♀️": EmojiWithSkinTones(baseEmoji: .femalePoliceOfficer, skinTones: [.medium]), + "💂🏽‍♂️": EmojiWithSkinTones(baseEmoji: .maleGuard, skinTones: [.medium]), + "💂🏽‍♀️": EmojiWithSkinTones(baseEmoji: .femaleGuard, skinTones: [.medium]), + "👷🏽‍♂️": EmojiWithSkinTones(baseEmoji: .maleConstructionWorker, skinTones: [.medium]), + "👷🏽‍♀️": EmojiWithSkinTones(baseEmoji: .femaleConstructionWorker, skinTones: [.medium]), + "👳🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manWearingTurban, skinTones: [.medium]), + "👳🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanWearingTurban, skinTones: [.medium]), + "👰🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manWithVeil, skinTones: [.medium]), + "👰🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanWithVeil, skinTones: [.medium]), + "💆🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manGettingMassage, skinTones: [.medium]), + "💆🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanGettingMassage, skinTones: [.medium]), + "💇🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manGettingHaircut, skinTones: [.medium]), + "💇🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanGettingHaircut, skinTones: [.medium]), + "👱🏾‍♀️": EmojiWithSkinTones(baseEmoji: .blondHairedWoman, skinTones: [.mediumDark]), + "👱🏾‍♂️": EmojiWithSkinTones(baseEmoji: .blondHairedMan, skinTones: [.mediumDark]), + "💁🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manTippingHand, skinTones: [.mediumDark]), + "💁🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanTippingHand, skinTones: [.mediumDark]), + "👮🏾‍♂️": EmojiWithSkinTones(baseEmoji: .malePoliceOfficer, skinTones: [.mediumDark]), + "👮🏾‍♀️": EmojiWithSkinTones(baseEmoji: .femalePoliceOfficer, skinTones: [.mediumDark]), + "💂🏾‍♂️": EmojiWithSkinTones(baseEmoji: .maleGuard, skinTones: [.mediumDark]), + "💂🏾‍♀️": EmojiWithSkinTones(baseEmoji: .femaleGuard, skinTones: [.mediumDark]), + "👷🏾‍♂️": EmojiWithSkinTones(baseEmoji: .maleConstructionWorker, skinTones: [.mediumDark]), + "👷🏾‍♀️": EmojiWithSkinTones(baseEmoji: .femaleConstructionWorker, skinTones: [.mediumDark]), + "👳🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manWearingTurban, skinTones: [.mediumDark]), + "👳🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanWearingTurban, skinTones: [.mediumDark]), + "👰🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manWithVeil, skinTones: [.mediumDark]), + "👰🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanWithVeil, skinTones: [.mediumDark]), + "💆🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manGettingMassage, skinTones: [.mediumDark]), + "💆🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanGettingMassage, skinTones: [.mediumDark]), + "💇🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manGettingHaircut, skinTones: [.mediumDark]), + "💇🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanGettingHaircut, skinTones: [.mediumDark]), + "👱🏿‍♀️": EmojiWithSkinTones(baseEmoji: .blondHairedWoman, skinTones: [.dark]), + "👱🏿‍♂️": EmojiWithSkinTones(baseEmoji: .blondHairedMan, skinTones: [.dark]), + "💁🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manTippingHand, skinTones: [.dark]), + "💁🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanTippingHand, skinTones: [.dark]), + "👮🏿‍♂️": EmojiWithSkinTones(baseEmoji: .malePoliceOfficer, skinTones: [.dark]), + "👮🏿‍♀️": EmojiWithSkinTones(baseEmoji: .femalePoliceOfficer, skinTones: [.dark]), + "💂🏿‍♂️": EmojiWithSkinTones(baseEmoji: .maleGuard, skinTones: [.dark]), + "💂🏿‍♀️": EmojiWithSkinTones(baseEmoji: .femaleGuard, skinTones: [.dark]), + "👷🏿‍♂️": EmojiWithSkinTones(baseEmoji: .maleConstructionWorker, skinTones: [.dark]), + "👷🏿‍♀️": EmojiWithSkinTones(baseEmoji: .femaleConstructionWorker, skinTones: [.dark]), + "👳🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manWearingTurban, skinTones: [.dark]), + "👳🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanWearingTurban, skinTones: [.dark]), + "👰🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manWithVeil, skinTones: [.dark]), + "👰🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanWithVeil, skinTones: [.dark]), + "💆🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manGettingMassage, skinTones: [.dark]), + "💆🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanGettingMassage, skinTones: [.dark]), + "💇🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manGettingHaircut, skinTones: [.dark]), + "💇🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanGettingHaircut, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3392(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨🏻‍⚕️": EmojiWithSkinTones(baseEmoji: .maleDoctor, skinTones: [.light]), + "👩🏻‍⚕️": EmojiWithSkinTones(baseEmoji: .femaleDoctor, skinTones: [.light]), + "👨🏻‍⚖️": EmojiWithSkinTones(baseEmoji: .maleJudge, skinTones: [.light]), + "👩🏻‍⚖️": EmojiWithSkinTones(baseEmoji: .femaleJudge, skinTones: [.light]), + "👨🏼‍⚕️": EmojiWithSkinTones(baseEmoji: .maleDoctor, skinTones: [.mediumLight]), + "👩🏼‍⚕️": EmojiWithSkinTones(baseEmoji: .femaleDoctor, skinTones: [.mediumLight]), + "👨🏼‍⚖️": EmojiWithSkinTones(baseEmoji: .maleJudge, skinTones: [.mediumLight]), + "👩🏼‍⚖️": EmojiWithSkinTones(baseEmoji: .femaleJudge, skinTones: [.mediumLight]), + "👨🏽‍⚕️": EmojiWithSkinTones(baseEmoji: .maleDoctor, skinTones: [.medium]), + "👩🏽‍⚕️": EmojiWithSkinTones(baseEmoji: .femaleDoctor, skinTones: [.medium]), + "👨🏽‍⚖️": EmojiWithSkinTones(baseEmoji: .maleJudge, skinTones: [.medium]), + "👩🏽‍⚖️": EmojiWithSkinTones(baseEmoji: .femaleJudge, skinTones: [.medium]), + "👨🏾‍⚕️": EmojiWithSkinTones(baseEmoji: .maleDoctor, skinTones: [.mediumDark]), + "👩🏾‍⚕️": EmojiWithSkinTones(baseEmoji: .femaleDoctor, skinTones: [.mediumDark]), + "👨🏾‍⚖️": EmojiWithSkinTones(baseEmoji: .maleJudge, skinTones: [.mediumDark]), + "👩🏾‍⚖️": EmojiWithSkinTones(baseEmoji: .femaleJudge, skinTones: [.mediumDark]), + "👨🏿‍⚕️": EmojiWithSkinTones(baseEmoji: .maleDoctor, skinTones: [.dark]), + "👩🏿‍⚕️": EmojiWithSkinTones(baseEmoji: .femaleDoctor, skinTones: [.dark]), + "👨🏿‍⚖️": EmojiWithSkinTones(baseEmoji: .maleJudge, skinTones: [.dark]), + "👩🏿‍⚖️": EmojiWithSkinTones(baseEmoji: .femaleJudge, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3393(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨🏻‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.light]), + "👩🏻‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.light]), + "👨🏼‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.mediumLight]), + "👩🏼‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.mediumLight]), + "👨🏽‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.medium]), + "👩🏽‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.medium]), + "👨🏾‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.mediumDark]), + "👩🏾‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.mediumDark]), + "👨🏿‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.dark]), + "👩🏿‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3394(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🕵🏻‍♂️": EmojiWithSkinTones(baseEmoji: .maleDetective, skinTones: [.light]), + "🕵🏻‍♀️": EmojiWithSkinTones(baseEmoji: .femaleDetective, skinTones: [.light]), + "🕵🏼‍♂️": EmojiWithSkinTones(baseEmoji: .maleDetective, skinTones: [.mediumLight]), + "🕵🏼‍♀️": EmojiWithSkinTones(baseEmoji: .femaleDetective, skinTones: [.mediumLight]), + "🕵🏽‍♂️": EmojiWithSkinTones(baseEmoji: .maleDetective, skinTones: [.medium]), + "🕵🏽‍♀️": EmojiWithSkinTones(baseEmoji: .femaleDetective, skinTones: [.medium]), + "🕵🏾‍♂️": EmojiWithSkinTones(baseEmoji: .maleDetective, skinTones: [.mediumDark]), + "🕵🏾‍♀️": EmojiWithSkinTones(baseEmoji: .femaleDetective, skinTones: [.mediumDark]), + "🕵🏿‍♂️": EmojiWithSkinTones(baseEmoji: .maleDetective, skinTones: [.dark]), + "🕵🏿‍♀️": EmojiWithSkinTones(baseEmoji: .femaleDetective, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3396(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🙍🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manFrowning, skinTones: [.light]), + "🙍🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanFrowning, skinTones: [.light]), + "🙎🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manPouting, skinTones: [.light]), + "🙎🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanPouting, skinTones: [.light]), + "🙅🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manGesturingNo, skinTones: [.light]), + "🙅🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanGesturingNo, skinTones: [.light]), + "🙆🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manGesturingOk, skinTones: [.light]), + "🙆🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanGesturingOk, skinTones: [.light]), + "🙋🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manRaisingHand, skinTones: [.light]), + "🙋🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanRaisingHand, skinTones: [.light]), + "🙇🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manBowing, skinTones: [.light]), + "🙇🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanBowing, skinTones: [.light]), + "🙍🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manFrowning, skinTones: [.mediumLight]), + "🙍🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanFrowning, skinTones: [.mediumLight]), + "🙎🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manPouting, skinTones: [.mediumLight]), + "🙎🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanPouting, skinTones: [.mediumLight]), + "🙅🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manGesturingNo, skinTones: [.mediumLight]), + "🙅🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanGesturingNo, skinTones: [.mediumLight]), + "🙆🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manGesturingOk, skinTones: [.mediumLight]), + "🙆🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanGesturingOk, skinTones: [.mediumLight]), + "🙋🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manRaisingHand, skinTones: [.mediumLight]), + "🙋🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanRaisingHand, skinTones: [.mediumLight]), + "🙇🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manBowing, skinTones: [.mediumLight]), + "🙇🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanBowing, skinTones: [.mediumLight]), + "🙍🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manFrowning, skinTones: [.medium]), + "🙍🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanFrowning, skinTones: [.medium]), + "🙎🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manPouting, skinTones: [.medium]), + "🙎🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanPouting, skinTones: [.medium]), + "🙅🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manGesturingNo, skinTones: [.medium]), + "🙅🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanGesturingNo, skinTones: [.medium]), + "🙆🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manGesturingOk, skinTones: [.medium]), + "🙆🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanGesturingOk, skinTones: [.medium]), + "🙋🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manRaisingHand, skinTones: [.medium]), + "🙋🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanRaisingHand, skinTones: [.medium]), + "🙇🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manBowing, skinTones: [.medium]), + "🙇🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanBowing, skinTones: [.medium]), + "🙍🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manFrowning, skinTones: [.mediumDark]), + "🙍🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanFrowning, skinTones: [.mediumDark]), + "🙎🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manPouting, skinTones: [.mediumDark]), + "🙎🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanPouting, skinTones: [.mediumDark]), + "🙅🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manGesturingNo, skinTones: [.mediumDark]), + "🙅🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanGesturingNo, skinTones: [.mediumDark]), + "🙆🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manGesturingOk, skinTones: [.mediumDark]), + "🙆🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanGesturingOk, skinTones: [.mediumDark]), + "🙋🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manRaisingHand, skinTones: [.mediumDark]), + "🙋🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanRaisingHand, skinTones: [.mediumDark]), + "🙇🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manBowing, skinTones: [.mediumDark]), + "🙇🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanBowing, skinTones: [.mediumDark]), + "🙍🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manFrowning, skinTones: [.dark]), + "🙍🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanFrowning, skinTones: [.dark]), + "🙎🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manPouting, skinTones: [.dark]), + "🙎🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanPouting, skinTones: [.dark]), + "🙅🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manGesturingNo, skinTones: [.dark]), + "🙅🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanGesturingNo, skinTones: [.dark]), + "🙆🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manGesturingOk, skinTones: [.dark]), + "🙆🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanGesturingOk, skinTones: [.dark]), + "🙋🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manRaisingHand, skinTones: [.dark]), + "🙋🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanRaisingHand, skinTones: [.dark]), + "🙇🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manBowing, skinTones: [.dark]), + "🙇🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanBowing, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3397(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🚶🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manWalking, skinTones: [.light]), + "🚶🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanWalking, skinTones: [.light]), + "🚣🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manRowingBoat, skinTones: [.light]), + "🚣🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanRowingBoat, skinTones: [.light]), + "🚴🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manBiking, skinTones: [.light]), + "🚴🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanBiking, skinTones: [.light]), + "🚵🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manMountainBiking, skinTones: [.light]), + "🚵🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanMountainBiking, skinTones: [.light]), + "🚶🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manWalking, skinTones: [.mediumLight]), + "🚶🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanWalking, skinTones: [.mediumLight]), + "🚣🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manRowingBoat, skinTones: [.mediumLight]), + "🚣🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanRowingBoat, skinTones: [.mediumLight]), + "🚴🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manBiking, skinTones: [.mediumLight]), + "🚴🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanBiking, skinTones: [.mediumLight]), + "🚵🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manMountainBiking, skinTones: [.mediumLight]), + "🚵🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanMountainBiking, skinTones: [.mediumLight]), + "🚶🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manWalking, skinTones: [.medium]), + "🚶🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanWalking, skinTones: [.medium]), + "🚣🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manRowingBoat, skinTones: [.medium]), + "🚣🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanRowingBoat, skinTones: [.medium]), + "🚴🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manBiking, skinTones: [.medium]), + "🚴🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanBiking, skinTones: [.medium]), + "🚵🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manMountainBiking, skinTones: [.medium]), + "🚵🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanMountainBiking, skinTones: [.medium]), + "🚶🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manWalking, skinTones: [.mediumDark]), + "🚶🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanWalking, skinTones: [.mediumDark]), + "🚣🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manRowingBoat, skinTones: [.mediumDark]), + "🚣🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanRowingBoat, skinTones: [.mediumDark]), + "🚴🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manBiking, skinTones: [.mediumDark]), + "🚴🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanBiking, skinTones: [.mediumDark]), + "🚵🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manMountainBiking, skinTones: [.mediumDark]), + "🚵🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanMountainBiking, skinTones: [.mediumDark]), + "🚶🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manWalking, skinTones: [.dark]), + "🚶🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanWalking, skinTones: [.dark]), + "🚣🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manRowingBoat, skinTones: [.dark]), + "🚣🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanRowingBoat, skinTones: [.dark]), + "🚴🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manBiking, skinTones: [.dark]), + "🚴🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanBiking, skinTones: [.dark]), + "🚵🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manMountainBiking, skinTones: [.dark]), + "🚵🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanMountainBiking, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3403(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🤦🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manFacepalming, skinTones: [.light]), + "🤦🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanFacepalming, skinTones: [.light]), + "🤷🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manShrugging, skinTones: [.light]), + "🤷🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanShrugging, skinTones: [.light]), + "🤵🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manInTuxedo, skinTones: [.light]), + "🤵🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanInTuxedo, skinTones: [.light]), + "🤸🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manCartwheeling, skinTones: [.light]), + "🤸🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanCartwheeling, skinTones: [.light]), + "🤽🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manPlayingWaterPolo, skinTones: [.light]), + "🤽🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanPlayingWaterPolo, skinTones: [.light]), + "🤾🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manPlayingHandball, skinTones: [.light]), + "🤾🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanPlayingHandball, skinTones: [.light]), + "🤹🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manJuggling, skinTones: [.light]), + "🤹🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanJuggling, skinTones: [.light]), + "🤦🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manFacepalming, skinTones: [.mediumLight]), + "🤦🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanFacepalming, skinTones: [.mediumLight]), + "🤷🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manShrugging, skinTones: [.mediumLight]), + "🤷🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanShrugging, skinTones: [.mediumLight]), + "🤵🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manInTuxedo, skinTones: [.mediumLight]), + "🤵🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanInTuxedo, skinTones: [.mediumLight]), + "🤸🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manCartwheeling, skinTones: [.mediumLight]), + "🤸🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanCartwheeling, skinTones: [.mediumLight]), + "🤽🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manPlayingWaterPolo, skinTones: [.mediumLight]), + "🤽🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanPlayingWaterPolo, skinTones: [.mediumLight]), + "🤾🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manPlayingHandball, skinTones: [.mediumLight]), + "🤾🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanPlayingHandball, skinTones: [.mediumLight]), + "🤹🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manJuggling, skinTones: [.mediumLight]), + "🤹🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanJuggling, skinTones: [.mediumLight]), + "🤦🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manFacepalming, skinTones: [.medium]), + "🤦🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanFacepalming, skinTones: [.medium]), + "🤷🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manShrugging, skinTones: [.medium]), + "🤷🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanShrugging, skinTones: [.medium]), + "🤵🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manInTuxedo, skinTones: [.medium]), + "🤵🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanInTuxedo, skinTones: [.medium]), + "🤸🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manCartwheeling, skinTones: [.medium]), + "🤸🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanCartwheeling, skinTones: [.medium]), + "🤽🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manPlayingWaterPolo, skinTones: [.medium]), + "🤽🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanPlayingWaterPolo, skinTones: [.medium]), + "🤾🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manPlayingHandball, skinTones: [.medium]), + "🤾🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanPlayingHandball, skinTones: [.medium]), + "🤹🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manJuggling, skinTones: [.medium]), + "🤹🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanJuggling, skinTones: [.medium]), + "🤦🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manFacepalming, skinTones: [.mediumDark]), + "🤦🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanFacepalming, skinTones: [.mediumDark]), + "🤷🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manShrugging, skinTones: [.mediumDark]), + "🤷🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanShrugging, skinTones: [.mediumDark]), + "🤵🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manInTuxedo, skinTones: [.mediumDark]), + "🤵🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanInTuxedo, skinTones: [.mediumDark]), + "🤸🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manCartwheeling, skinTones: [.mediumDark]), + "🤸🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanCartwheeling, skinTones: [.mediumDark]), + "🤽🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manPlayingWaterPolo, skinTones: [.mediumDark]), + "🤽🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanPlayingWaterPolo, skinTones: [.mediumDark]), + "🤾🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manPlayingHandball, skinTones: [.mediumDark]), + "🤾🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanPlayingHandball, skinTones: [.mediumDark]), + "🤹🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manJuggling, skinTones: [.mediumDark]), + "🤹🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanJuggling, skinTones: [.mediumDark]), + "🤦🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manFacepalming, skinTones: [.dark]), + "🤦🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanFacepalming, skinTones: [.dark]), + "🤷🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manShrugging, skinTones: [.dark]), + "🤷🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanShrugging, skinTones: [.dark]), + "🤵🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manInTuxedo, skinTones: [.dark]), + "🤵🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanInTuxedo, skinTones: [.dark]), + "🤸🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manCartwheeling, skinTones: [.dark]), + "🤸🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanCartwheeling, skinTones: [.dark]), + "🤽🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manPlayingWaterPolo, skinTones: [.dark]), + "🤽🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanPlayingWaterPolo, skinTones: [.dark]), + "🤾🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manPlayingHandball, skinTones: [.dark]), + "🤾🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanPlayingHandball, skinTones: [.dark]), + "🤹🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manJuggling, skinTones: [.dark]), + "🤹🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanJuggling, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3404(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🦸🏻‍♂️": EmojiWithSkinTones(baseEmoji: .maleSuperhero, skinTones: [.light]), + "🦸🏻‍♀️": EmojiWithSkinTones(baseEmoji: .femaleSuperhero, skinTones: [.light]), + "🦹🏻‍♂️": EmojiWithSkinTones(baseEmoji: .maleSupervillain, skinTones: [.light]), + "🦹🏻‍♀️": EmojiWithSkinTones(baseEmoji: .femaleSupervillain, skinTones: [.light]), + "🦸🏼‍♂️": EmojiWithSkinTones(baseEmoji: .maleSuperhero, skinTones: [.mediumLight]), + "🦸🏼‍♀️": EmojiWithSkinTones(baseEmoji: .femaleSuperhero, skinTones: [.mediumLight]), + "🦹🏼‍♂️": EmojiWithSkinTones(baseEmoji: .maleSupervillain, skinTones: [.mediumLight]), + "🦹🏼‍♀️": EmojiWithSkinTones(baseEmoji: .femaleSupervillain, skinTones: [.mediumLight]), + "🦸🏽‍♂️": EmojiWithSkinTones(baseEmoji: .maleSuperhero, skinTones: [.medium]), + "🦸🏽‍♀️": EmojiWithSkinTones(baseEmoji: .femaleSuperhero, skinTones: [.medium]), + "🦹🏽‍♀️": EmojiWithSkinTones(baseEmoji: .femaleSupervillain, skinTones: [.medium]), + "🦸🏾‍♀️": EmojiWithSkinTones(baseEmoji: .femaleSuperhero, skinTones: [.mediumDark]), + "🦹🏾‍♀️": EmojiWithSkinTones(baseEmoji: .femaleSupervillain, skinTones: [.mediumDark]), + "🦸🏿‍♀️": EmojiWithSkinTones(baseEmoji: .femaleSuperhero, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3405(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧔🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manWithBeard, skinTones: [.light]), + "🧔🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanWithBeard, skinTones: [.light]), + "🧏🏻‍♂️": EmojiWithSkinTones(baseEmoji: .deafMan, skinTones: [.light]), + "🧏🏻‍♀️": EmojiWithSkinTones(baseEmoji: .deafWoman, skinTones: [.light]), + "🧙🏻‍♂️": EmojiWithSkinTones(baseEmoji: .maleMage, skinTones: [.light]), + "🧙🏻‍♀️": EmojiWithSkinTones(baseEmoji: .femaleMage, skinTones: [.light]), + "🧚🏻‍♂️": EmojiWithSkinTones(baseEmoji: .maleFairy, skinTones: [.light]), + "🧚🏻‍♀️": EmojiWithSkinTones(baseEmoji: .femaleFairy, skinTones: [.light]), + "🧛🏻‍♂️": EmojiWithSkinTones(baseEmoji: .maleVampire, skinTones: [.light]), + "🧛🏻‍♀️": EmojiWithSkinTones(baseEmoji: .femaleVampire, skinTones: [.light]), + "🧜🏻‍♂️": EmojiWithSkinTones(baseEmoji: .merman, skinTones: [.light]), + "🧜🏻‍♀️": EmojiWithSkinTones(baseEmoji: .mermaid, skinTones: [.light]), + "🧝🏻‍♂️": EmojiWithSkinTones(baseEmoji: .maleElf, skinTones: [.light]), + "🧝🏻‍♀️": EmojiWithSkinTones(baseEmoji: .femaleElf, skinTones: [.light]), + "🧍🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manStanding, skinTones: [.light]), + "🧍🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanStanding, skinTones: [.light]), + "🧎🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manKneeling, skinTones: [.light]), + "🧎🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanKneeling, skinTones: [.light]), + "🧖🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manInSteamyRoom, skinTones: [.light]), + "🧖🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanInSteamyRoom, skinTones: [.light]), + "🧗🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manClimbing, skinTones: [.light]), + "🧗🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanClimbing, skinTones: [.light]), + "🧘🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manInLotusPosition, skinTones: [.light]), + "🧘🏻‍♀️": EmojiWithSkinTones(baseEmoji: .womanInLotusPosition, skinTones: [.light]), + "🧔🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manWithBeard, skinTones: [.mediumLight]), + "🧔🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanWithBeard, skinTones: [.mediumLight]), + "🧏🏼‍♂️": EmojiWithSkinTones(baseEmoji: .deafMan, skinTones: [.mediumLight]), + "🧏🏼‍♀️": EmojiWithSkinTones(baseEmoji: .deafWoman, skinTones: [.mediumLight]), + "🧙🏼‍♂️": EmojiWithSkinTones(baseEmoji: .maleMage, skinTones: [.mediumLight]), + "🧙🏼‍♀️": EmojiWithSkinTones(baseEmoji: .femaleMage, skinTones: [.mediumLight]), + "🧚🏼‍♂️": EmojiWithSkinTones(baseEmoji: .maleFairy, skinTones: [.mediumLight]), + "🧚🏼‍♀️": EmojiWithSkinTones(baseEmoji: .femaleFairy, skinTones: [.mediumLight]), + "🧛🏼‍♂️": EmojiWithSkinTones(baseEmoji: .maleVampire, skinTones: [.mediumLight]), + "🧛🏼‍♀️": EmojiWithSkinTones(baseEmoji: .femaleVampire, skinTones: [.mediumLight]), + "🧜🏼‍♂️": EmojiWithSkinTones(baseEmoji: .merman, skinTones: [.mediumLight]), + "🧜🏼‍♀️": EmojiWithSkinTones(baseEmoji: .mermaid, skinTones: [.mediumLight]), + "🧝🏼‍♂️": EmojiWithSkinTones(baseEmoji: .maleElf, skinTones: [.mediumLight]), + "🧝🏼‍♀️": EmojiWithSkinTones(baseEmoji: .femaleElf, skinTones: [.mediumLight]), + "🧍🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manStanding, skinTones: [.mediumLight]), + "🧍🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanStanding, skinTones: [.mediumLight]), + "🧎🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manKneeling, skinTones: [.mediumLight]), + "🧎🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanKneeling, skinTones: [.mediumLight]), + "🧖🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manInSteamyRoom, skinTones: [.mediumLight]), + "🧖🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanInSteamyRoom, skinTones: [.mediumLight]), + "🧗🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manClimbing, skinTones: [.mediumLight]), + "🧗🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanClimbing, skinTones: [.mediumLight]), + "🧘🏼‍♂️": EmojiWithSkinTones(baseEmoji: .manInLotusPosition, skinTones: [.mediumLight]), + "🧘🏼‍♀️": EmojiWithSkinTones(baseEmoji: .womanInLotusPosition, skinTones: [.mediumLight]), + "🧔🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manWithBeard, skinTones: [.medium]), + "🧔🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanWithBeard, skinTones: [.medium]), + "🧏🏽‍♂️": EmojiWithSkinTones(baseEmoji: .deafMan, skinTones: [.medium]), + "🧏🏽‍♀️": EmojiWithSkinTones(baseEmoji: .deafWoman, skinTones: [.medium]), + "🦹🏽‍♂️": EmojiWithSkinTones(baseEmoji: .maleSupervillain, skinTones: [.medium]), + "🧙🏽‍♂️": EmojiWithSkinTones(baseEmoji: .maleMage, skinTones: [.medium]), + "🧙🏽‍♀️": EmojiWithSkinTones(baseEmoji: .femaleMage, skinTones: [.medium]), + "🧚🏽‍♂️": EmojiWithSkinTones(baseEmoji: .maleFairy, skinTones: [.medium]), + "🧚🏽‍♀️": EmojiWithSkinTones(baseEmoji: .femaleFairy, skinTones: [.medium]), + "🧛🏽‍♂️": EmojiWithSkinTones(baseEmoji: .maleVampire, skinTones: [.medium]), + "🧛🏽‍♀️": EmojiWithSkinTones(baseEmoji: .femaleVampire, skinTones: [.medium]), + "🧜🏽‍♂️": EmojiWithSkinTones(baseEmoji: .merman, skinTones: [.medium]), + "🧜🏽‍♀️": EmojiWithSkinTones(baseEmoji: .mermaid, skinTones: [.medium]), + "🧝🏽‍♂️": EmojiWithSkinTones(baseEmoji: .maleElf, skinTones: [.medium]), + "🧝🏽‍♀️": EmojiWithSkinTones(baseEmoji: .femaleElf, skinTones: [.medium]), + "🧍🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manStanding, skinTones: [.medium]), + "🧍🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanStanding, skinTones: [.medium]), + "🧎🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manKneeling, skinTones: [.medium]), + "🧎🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanKneeling, skinTones: [.medium]), + "🧖🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manInSteamyRoom, skinTones: [.medium]), + "🧖🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanInSteamyRoom, skinTones: [.medium]), + "🧗🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manClimbing, skinTones: [.medium]), + "🧗🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanClimbing, skinTones: [.medium]), + "🧘🏽‍♂️": EmojiWithSkinTones(baseEmoji: .manInLotusPosition, skinTones: [.medium]), + "🧘🏽‍♀️": EmojiWithSkinTones(baseEmoji: .womanInLotusPosition, skinTones: [.medium]), + "🧔🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manWithBeard, skinTones: [.mediumDark]), + "🧔🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanWithBeard, skinTones: [.mediumDark]), + "🧏🏾‍♂️": EmojiWithSkinTones(baseEmoji: .deafMan, skinTones: [.mediumDark]), + "🧏🏾‍♀️": EmojiWithSkinTones(baseEmoji: .deafWoman, skinTones: [.mediumDark]), + "🦸🏾‍♂️": EmojiWithSkinTones(baseEmoji: .maleSuperhero, skinTones: [.mediumDark]), + "🦹🏾‍♂️": EmojiWithSkinTones(baseEmoji: .maleSupervillain, skinTones: [.mediumDark]), + "🧙🏾‍♂️": EmojiWithSkinTones(baseEmoji: .maleMage, skinTones: [.mediumDark]), + "🧙🏾‍♀️": EmojiWithSkinTones(baseEmoji: .femaleMage, skinTones: [.mediumDark]), + "🧚🏾‍♂️": EmojiWithSkinTones(baseEmoji: .maleFairy, skinTones: [.mediumDark]), + "🧚🏾‍♀️": EmojiWithSkinTones(baseEmoji: .femaleFairy, skinTones: [.mediumDark]), + "🧛🏾‍♂️": EmojiWithSkinTones(baseEmoji: .maleVampire, skinTones: [.mediumDark]), + "🧛🏾‍♀️": EmojiWithSkinTones(baseEmoji: .femaleVampire, skinTones: [.mediumDark]), + "🧜🏾‍♂️": EmojiWithSkinTones(baseEmoji: .merman, skinTones: [.mediumDark]), + "🧜🏾‍♀️": EmojiWithSkinTones(baseEmoji: .mermaid, skinTones: [.mediumDark]), + "🧝🏾‍♂️": EmojiWithSkinTones(baseEmoji: .maleElf, skinTones: [.mediumDark]), + "🧝🏾‍♀️": EmojiWithSkinTones(baseEmoji: .femaleElf, skinTones: [.mediumDark]), + "🧍🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manStanding, skinTones: [.mediumDark]), + "🧍🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanStanding, skinTones: [.mediumDark]), + "🧎🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manKneeling, skinTones: [.mediumDark]), + "🧎🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanKneeling, skinTones: [.mediumDark]), + "🧖🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manInSteamyRoom, skinTones: [.mediumDark]), + "🧖🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanInSteamyRoom, skinTones: [.mediumDark]), + "🧗🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manClimbing, skinTones: [.mediumDark]), + "🧗🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanClimbing, skinTones: [.mediumDark]), + "🧘🏾‍♂️": EmojiWithSkinTones(baseEmoji: .manInLotusPosition, skinTones: [.mediumDark]), + "🧘🏾‍♀️": EmojiWithSkinTones(baseEmoji: .womanInLotusPosition, skinTones: [.mediumDark]), + "🧔🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manWithBeard, skinTones: [.dark]), + "🧔🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanWithBeard, skinTones: [.dark]), + "🧏🏿‍♂️": EmojiWithSkinTones(baseEmoji: .deafMan, skinTones: [.dark]), + "🧏🏿‍♀️": EmojiWithSkinTones(baseEmoji: .deafWoman, skinTones: [.dark]), + "🦸🏿‍♂️": EmojiWithSkinTones(baseEmoji: .maleSuperhero, skinTones: [.dark]), + "🦹🏿‍♂️": EmojiWithSkinTones(baseEmoji: .maleSupervillain, skinTones: [.dark]), + "🦹🏿‍♀️": EmojiWithSkinTones(baseEmoji: .femaleSupervillain, skinTones: [.dark]), + "🧙🏿‍♂️": EmojiWithSkinTones(baseEmoji: .maleMage, skinTones: [.dark]), + "🧙🏿‍♀️": EmojiWithSkinTones(baseEmoji: .femaleMage, skinTones: [.dark]), + "🧚🏿‍♂️": EmojiWithSkinTones(baseEmoji: .maleFairy, skinTones: [.dark]), + "🧚🏿‍♀️": EmojiWithSkinTones(baseEmoji: .femaleFairy, skinTones: [.dark]), + "🧛🏿‍♂️": EmojiWithSkinTones(baseEmoji: .maleVampire, skinTones: [.dark]), + "🧛🏿‍♀️": EmojiWithSkinTones(baseEmoji: .femaleVampire, skinTones: [.dark]), + "🧜🏿‍♂️": EmojiWithSkinTones(baseEmoji: .merman, skinTones: [.dark]), + "🧜🏿‍♀️": EmojiWithSkinTones(baseEmoji: .mermaid, skinTones: [.dark]), + "🧝🏿‍♂️": EmojiWithSkinTones(baseEmoji: .maleElf, skinTones: [.dark]), + "🧝🏿‍♀️": EmojiWithSkinTones(baseEmoji: .femaleElf, skinTones: [.dark]), + "🧍🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manStanding, skinTones: [.dark]), + "🧍🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanStanding, skinTones: [.dark]), + "🧎🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manKneeling, skinTones: [.dark]), + "🧎🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanKneeling, skinTones: [.dark]), + "🧖🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manInSteamyRoom, skinTones: [.dark]), + "🧖🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanInSteamyRoom, skinTones: [.dark]), + "🧗🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manClimbing, skinTones: [.dark]), + "🧗🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanClimbing, skinTones: [.dark]), + "🧘🏿‍♂️": EmojiWithSkinTones(baseEmoji: .manInLotusPosition, skinTones: [.dark]), + "🧘🏿‍♀️": EmojiWithSkinTones(baseEmoji: .womanInLotusPosition, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3406(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍⚕️": EmojiWithSkinTones(baseEmoji: .healthWorker, skinTones: [.light]), + "🧑🏻‍⚖️": EmojiWithSkinTones(baseEmoji: .judge, skinTones: [.light]), + "🧑🏼‍⚕️": EmojiWithSkinTones(baseEmoji: .healthWorker, skinTones: [.mediumLight]), + "🧑🏼‍⚖️": EmojiWithSkinTones(baseEmoji: .judge, skinTones: [.mediumLight]), + "🧑🏽‍⚕️": EmojiWithSkinTones(baseEmoji: .healthWorker, skinTones: [.medium]), + "🧑🏽‍⚖️": EmojiWithSkinTones(baseEmoji: .judge, skinTones: [.medium]), + "🧑🏾‍⚕️": EmojiWithSkinTones(baseEmoji: .healthWorker, skinTones: [.mediumDark]), + "🧑🏾‍⚖️": EmojiWithSkinTones(baseEmoji: .judge, skinTones: [.mediumDark]), + "🧑🏿‍⚕️": EmojiWithSkinTones(baseEmoji: .healthWorker, skinTones: [.dark]), + "🧑🏿‍⚖️": EmojiWithSkinTones(baseEmoji: .judge, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3407(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍✈️": EmojiWithSkinTones(baseEmoji: .pilot, skinTones: [.light]), + "🧑🏼‍✈️": EmojiWithSkinTones(baseEmoji: .pilot, skinTones: [.mediumLight]), + "🧑🏽‍✈️": EmojiWithSkinTones(baseEmoji: .pilot, skinTones: [.medium]), + "🧑🏾‍✈️": EmojiWithSkinTones(baseEmoji: .pilot, skinTones: [.mediumDark]), + "🧑🏿‍✈️": EmojiWithSkinTones(baseEmoji: .pilot, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3477(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👩‍❤️‍👨": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: nil), + "👨‍❤️‍👨": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: nil), + "👩‍❤️‍👩": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3921(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨🏻‍🎓": EmojiWithSkinTones(baseEmoji: .maleStudent, skinTones: [.light]), + "👩🏻‍🎓": EmojiWithSkinTones(baseEmoji: .femaleStudent, skinTones: [.light]), + "👨🏻‍🌾": EmojiWithSkinTones(baseEmoji: .maleFarmer, skinTones: [.light]), + "👩🏻‍🌾": EmojiWithSkinTones(baseEmoji: .femaleFarmer, skinTones: [.light]), + "👨🏻‍🍳": EmojiWithSkinTones(baseEmoji: .maleCook, skinTones: [.light]), + "👩🏻‍🍳": EmojiWithSkinTones(baseEmoji: .femaleCook, skinTones: [.light]), + "👩🏻‍🍼": EmojiWithSkinTones(baseEmoji: .womanFeedingBaby, skinTones: [.light]), + "👨🏻‍🍼": EmojiWithSkinTones(baseEmoji: .manFeedingBaby, skinTones: [.light]), + "👨🏼‍🎓": EmojiWithSkinTones(baseEmoji: .maleStudent, skinTones: [.mediumLight]), + "👩🏼‍🎓": EmojiWithSkinTones(baseEmoji: .femaleStudent, skinTones: [.mediumLight]), + "👨🏼‍🌾": EmojiWithSkinTones(baseEmoji: .maleFarmer, skinTones: [.mediumLight]), + "👩🏼‍🌾": EmojiWithSkinTones(baseEmoji: .femaleFarmer, skinTones: [.mediumLight]), + "👨🏼‍🍳": EmojiWithSkinTones(baseEmoji: .maleCook, skinTones: [.mediumLight]), + "👩🏼‍🍳": EmojiWithSkinTones(baseEmoji: .femaleCook, skinTones: [.mediumLight]), + "👩🏼‍🍼": EmojiWithSkinTones(baseEmoji: .womanFeedingBaby, skinTones: [.mediumLight]), + "👨🏼‍🍼": EmojiWithSkinTones(baseEmoji: .manFeedingBaby, skinTones: [.mediumLight]), + "👨🏽‍🎓": EmojiWithSkinTones(baseEmoji: .maleStudent, skinTones: [.medium]), + "👩🏽‍🎓": EmojiWithSkinTones(baseEmoji: .femaleStudent, skinTones: [.medium]), + "👨🏽‍🌾": EmojiWithSkinTones(baseEmoji: .maleFarmer, skinTones: [.medium]), + "👩🏽‍🌾": EmojiWithSkinTones(baseEmoji: .femaleFarmer, skinTones: [.medium]), + "👨🏽‍🍳": EmojiWithSkinTones(baseEmoji: .maleCook, skinTones: [.medium]), + "👩🏽‍🍳": EmojiWithSkinTones(baseEmoji: .femaleCook, skinTones: [.medium]), + "👩🏽‍🍼": EmojiWithSkinTones(baseEmoji: .womanFeedingBaby, skinTones: [.medium]), + "👨🏽‍🍼": EmojiWithSkinTones(baseEmoji: .manFeedingBaby, skinTones: [.medium]), + "👨🏾‍🎓": EmojiWithSkinTones(baseEmoji: .maleStudent, skinTones: [.mediumDark]), + "👩🏾‍🎓": EmojiWithSkinTones(baseEmoji: .femaleStudent, skinTones: [.mediumDark]), + "👨🏾‍🌾": EmojiWithSkinTones(baseEmoji: .maleFarmer, skinTones: [.mediumDark]), + "👩🏾‍🌾": EmojiWithSkinTones(baseEmoji: .femaleFarmer, skinTones: [.mediumDark]), + "👨🏾‍🍳": EmojiWithSkinTones(baseEmoji: .maleCook, skinTones: [.mediumDark]), + "👩🏾‍🍳": EmojiWithSkinTones(baseEmoji: .femaleCook, skinTones: [.mediumDark]), + "👩🏾‍🍼": EmojiWithSkinTones(baseEmoji: .womanFeedingBaby, skinTones: [.mediumDark]), + "👨🏾‍🍼": EmojiWithSkinTones(baseEmoji: .manFeedingBaby, skinTones: [.mediumDark]), + "👨🏿‍🎓": EmojiWithSkinTones(baseEmoji: .maleStudent, skinTones: [.dark]), + "👨🏿‍🌾": EmojiWithSkinTones(baseEmoji: .maleFarmer, skinTones: [.dark]), + "👩🏿‍🌾": EmojiWithSkinTones(baseEmoji: .femaleFarmer, skinTones: [.dark]), + "👨🏿‍🍳": EmojiWithSkinTones(baseEmoji: .maleCook, skinTones: [.dark]), + "👩🏿‍🍳": EmojiWithSkinTones(baseEmoji: .femaleCook, skinTones: [.dark]), + "👩🏿‍🍼": EmojiWithSkinTones(baseEmoji: .womanFeedingBaby, skinTones: [.dark]), + "👨🏿‍🍼": EmojiWithSkinTones(baseEmoji: .manFeedingBaby, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3922(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨🏻‍🏫": EmojiWithSkinTones(baseEmoji: .maleTeacher, skinTones: [.light]), + "👩🏻‍🏫": EmojiWithSkinTones(baseEmoji: .femaleTeacher, skinTones: [.light]), + "👨🏻‍🏭": EmojiWithSkinTones(baseEmoji: .maleFactoryWorker, skinTones: [.light]), + "👩🏻‍🏭": EmojiWithSkinTones(baseEmoji: .femaleFactoryWorker, skinTones: [.light]), + "👨🏻‍🎤": EmojiWithSkinTones(baseEmoji: .maleSinger, skinTones: [.light]), + "👩🏻‍🎤": EmojiWithSkinTones(baseEmoji: .femaleSinger, skinTones: [.light]), + "👨🏻‍🎨": EmojiWithSkinTones(baseEmoji: .maleArtist, skinTones: [.light]), + "👩🏻‍🎨": EmojiWithSkinTones(baseEmoji: .femaleArtist, skinTones: [.light]), + "👨🏼‍🏫": EmojiWithSkinTones(baseEmoji: .maleTeacher, skinTones: [.mediumLight]), + "👩🏼‍🏫": EmojiWithSkinTones(baseEmoji: .femaleTeacher, skinTones: [.mediumLight]), + "👨🏼‍🏭": EmojiWithSkinTones(baseEmoji: .maleFactoryWorker, skinTones: [.mediumLight]), + "👩🏼‍🏭": EmojiWithSkinTones(baseEmoji: .femaleFactoryWorker, skinTones: [.mediumLight]), + "👨🏼‍🎤": EmojiWithSkinTones(baseEmoji: .maleSinger, skinTones: [.mediumLight]), + "👩🏼‍🎤": EmojiWithSkinTones(baseEmoji: .femaleSinger, skinTones: [.mediumLight]), + "👨🏼‍🎨": EmojiWithSkinTones(baseEmoji: .maleArtist, skinTones: [.mediumLight]), + "👩🏼‍🎨": EmojiWithSkinTones(baseEmoji: .femaleArtist, skinTones: [.mediumLight]), + "👨🏽‍🏫": EmojiWithSkinTones(baseEmoji: .maleTeacher, skinTones: [.medium]), + "👩🏽‍🏫": EmojiWithSkinTones(baseEmoji: .femaleTeacher, skinTones: [.medium]), + "👨🏽‍🏭": EmojiWithSkinTones(baseEmoji: .maleFactoryWorker, skinTones: [.medium]), + "👩🏽‍🏭": EmojiWithSkinTones(baseEmoji: .femaleFactoryWorker, skinTones: [.medium]), + "👨🏽‍🎤": EmojiWithSkinTones(baseEmoji: .maleSinger, skinTones: [.medium]), + "👩🏽‍🎤": EmojiWithSkinTones(baseEmoji: .femaleSinger, skinTones: [.medium]), + "👨🏽‍🎨": EmojiWithSkinTones(baseEmoji: .maleArtist, skinTones: [.medium]), + "👩🏽‍🎨": EmojiWithSkinTones(baseEmoji: .femaleArtist, skinTones: [.medium]), + "👨🏾‍🏫": EmojiWithSkinTones(baseEmoji: .maleTeacher, skinTones: [.mediumDark]), + "👩🏾‍🏫": EmojiWithSkinTones(baseEmoji: .femaleTeacher, skinTones: [.mediumDark]), + "👨🏾‍🏭": EmojiWithSkinTones(baseEmoji: .maleFactoryWorker, skinTones: [.mediumDark]), + "👩🏾‍🏭": EmojiWithSkinTones(baseEmoji: .femaleFactoryWorker, skinTones: [.mediumDark]), + "👨🏾‍🎤": EmojiWithSkinTones(baseEmoji: .maleSinger, skinTones: [.mediumDark]), + "👩🏾‍🎤": EmojiWithSkinTones(baseEmoji: .femaleSinger, skinTones: [.mediumDark]), + "👨🏾‍🎨": EmojiWithSkinTones(baseEmoji: .maleArtist, skinTones: [.mediumDark]), + "👩🏾‍🎨": EmojiWithSkinTones(baseEmoji: .femaleArtist, skinTones: [.mediumDark]), + "👩🏿‍🎓": EmojiWithSkinTones(baseEmoji: .femaleStudent, skinTones: [.dark]), + "👨🏿‍🏫": EmojiWithSkinTones(baseEmoji: .maleTeacher, skinTones: [.dark]), + "👩🏿‍🏫": EmojiWithSkinTones(baseEmoji: .femaleTeacher, skinTones: [.dark]), + "👨🏿‍🏭": EmojiWithSkinTones(baseEmoji: .maleFactoryWorker, skinTones: [.dark]), + "👩🏿‍🏭": EmojiWithSkinTones(baseEmoji: .femaleFactoryWorker, skinTones: [.dark]), + "👨🏿‍🎤": EmojiWithSkinTones(baseEmoji: .maleSinger, skinTones: [.dark]), + "👩🏿‍🎤": EmojiWithSkinTones(baseEmoji: .femaleSinger, skinTones: [.dark]), + "👨🏿‍🎨": EmojiWithSkinTones(baseEmoji: .maleArtist, skinTones: [.dark]), + "👩🏿‍🎨": EmojiWithSkinTones(baseEmoji: .femaleArtist, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3924(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨🏻‍💼": EmojiWithSkinTones(baseEmoji: .maleOfficeWorker, skinTones: [.light]), + "👩🏻‍💼": EmojiWithSkinTones(baseEmoji: .femaleOfficeWorker, skinTones: [.light]), + "👨🏻‍💻": EmojiWithSkinTones(baseEmoji: .maleTechnologist, skinTones: [.light]), + "👩🏻‍💻": EmojiWithSkinTones(baseEmoji: .femaleTechnologist, skinTones: [.light]), + "👨🏼‍💼": EmojiWithSkinTones(baseEmoji: .maleOfficeWorker, skinTones: [.mediumLight]), + "👩🏼‍💼": EmojiWithSkinTones(baseEmoji: .femaleOfficeWorker, skinTones: [.mediumLight]), + "👨🏼‍💻": EmojiWithSkinTones(baseEmoji: .maleTechnologist, skinTones: [.mediumLight]), + "👩🏼‍💻": EmojiWithSkinTones(baseEmoji: .femaleTechnologist, skinTones: [.mediumLight]), + "👨🏽‍💼": EmojiWithSkinTones(baseEmoji: .maleOfficeWorker, skinTones: [.medium]), + "👩🏽‍💼": EmojiWithSkinTones(baseEmoji: .femaleOfficeWorker, skinTones: [.medium]), + "👨🏽‍💻": EmojiWithSkinTones(baseEmoji: .maleTechnologist, skinTones: [.medium]), + "👩🏽‍💻": EmojiWithSkinTones(baseEmoji: .femaleTechnologist, skinTones: [.medium]), + "👨🏾‍💼": EmojiWithSkinTones(baseEmoji: .maleOfficeWorker, skinTones: [.mediumDark]), + "👩🏾‍💼": EmojiWithSkinTones(baseEmoji: .femaleOfficeWorker, skinTones: [.mediumDark]), + "👨🏾‍💻": EmojiWithSkinTones(baseEmoji: .maleTechnologist, skinTones: [.mediumDark]), + "👩🏾‍💻": EmojiWithSkinTones(baseEmoji: .femaleTechnologist, skinTones: [.mediumDark]), + "👨🏿‍💼": EmojiWithSkinTones(baseEmoji: .maleOfficeWorker, skinTones: [.dark]), + "👩🏿‍💼": EmojiWithSkinTones(baseEmoji: .femaleOfficeWorker, skinTones: [.dark]), + "👨🏿‍💻": EmojiWithSkinTones(baseEmoji: .maleTechnologist, skinTones: [.dark]), + "👩🏿‍💻": EmojiWithSkinTones(baseEmoji: .femaleTechnologist, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3925(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨🏻‍🔧": EmojiWithSkinTones(baseEmoji: .maleMechanic, skinTones: [.light]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3926(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👩🏻‍🔧": EmojiWithSkinTones(baseEmoji: .femaleMechanic, skinTones: [.light]), + "👨🏻‍🔬": EmojiWithSkinTones(baseEmoji: .maleScientist, skinTones: [.light]), + "👩🏻‍🔬": EmojiWithSkinTones(baseEmoji: .femaleScientist, skinTones: [.light]), + "👨🏼‍🔧": EmojiWithSkinTones(baseEmoji: .maleMechanic, skinTones: [.mediumLight]), + "👩🏼‍🔧": EmojiWithSkinTones(baseEmoji: .femaleMechanic, skinTones: [.mediumLight]), + "👨🏼‍🔬": EmojiWithSkinTones(baseEmoji: .maleScientist, skinTones: [.mediumLight]), + "👩🏼‍🔬": EmojiWithSkinTones(baseEmoji: .femaleScientist, skinTones: [.mediumLight]), + "👨🏽‍🔧": EmojiWithSkinTones(baseEmoji: .maleMechanic, skinTones: [.medium]), + "👩🏽‍🔧": EmojiWithSkinTones(baseEmoji: .femaleMechanic, skinTones: [.medium]), + "👨🏽‍🔬": EmojiWithSkinTones(baseEmoji: .maleScientist, skinTones: [.medium]), + "👩🏽‍🔬": EmojiWithSkinTones(baseEmoji: .femaleScientist, skinTones: [.medium]), + "👨🏾‍🔧": EmojiWithSkinTones(baseEmoji: .maleMechanic, skinTones: [.mediumDark]), + "👩🏾‍🔧": EmojiWithSkinTones(baseEmoji: .femaleMechanic, skinTones: [.mediumDark]), + "👨🏾‍🔬": EmojiWithSkinTones(baseEmoji: .maleScientist, skinTones: [.mediumDark]), + "👩🏾‍🔬": EmojiWithSkinTones(baseEmoji: .femaleScientist, skinTones: [.mediumDark]), + "👨🏿‍🔧": EmojiWithSkinTones(baseEmoji: .maleMechanic, skinTones: [.dark]), + "👩🏿‍🔧": EmojiWithSkinTones(baseEmoji: .femaleMechanic, skinTones: [.dark]), + "👨🏿‍🔬": EmojiWithSkinTones(baseEmoji: .maleScientist, skinTones: [.dark]), + "👩🏿‍🔬": EmojiWithSkinTones(baseEmoji: .femaleScientist, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3929(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨🏻‍🚀": EmojiWithSkinTones(baseEmoji: .maleAstronaut, skinTones: [.light]), + "👩🏻‍🚀": EmojiWithSkinTones(baseEmoji: .femaleAstronaut, skinTones: [.light]), + "👨🏻‍🚒": EmojiWithSkinTones(baseEmoji: .maleFirefighter, skinTones: [.light]), + "👩🏻‍🚒": EmojiWithSkinTones(baseEmoji: .femaleFirefighter, skinTones: [.light]), + "👨🏼‍🚀": EmojiWithSkinTones(baseEmoji: .maleAstronaut, skinTones: [.mediumLight]), + "👩🏼‍🚀": EmojiWithSkinTones(baseEmoji: .femaleAstronaut, skinTones: [.mediumLight]), + "👨🏼‍🚒": EmojiWithSkinTones(baseEmoji: .maleFirefighter, skinTones: [.mediumLight]), + "👩🏼‍🚒": EmojiWithSkinTones(baseEmoji: .femaleFirefighter, skinTones: [.mediumLight]), + "👨🏽‍🚀": EmojiWithSkinTones(baseEmoji: .maleAstronaut, skinTones: [.medium]), + "👩🏽‍🚀": EmojiWithSkinTones(baseEmoji: .femaleAstronaut, skinTones: [.medium]), + "👨🏽‍🚒": EmojiWithSkinTones(baseEmoji: .maleFirefighter, skinTones: [.medium]), + "👩🏽‍🚒": EmojiWithSkinTones(baseEmoji: .femaleFirefighter, skinTones: [.medium]), + "👨🏾‍🚀": EmojiWithSkinTones(baseEmoji: .maleAstronaut, skinTones: [.mediumDark]), + "👩🏾‍🚀": EmojiWithSkinTones(baseEmoji: .femaleAstronaut, skinTones: [.mediumDark]), + "👨🏾‍🚒": EmojiWithSkinTones(baseEmoji: .maleFirefighter, skinTones: [.mediumDark]), + "👩🏾‍🚒": EmojiWithSkinTones(baseEmoji: .femaleFirefighter, skinTones: [.mediumDark]), + "👨🏿‍🚀": EmojiWithSkinTones(baseEmoji: .maleAstronaut, skinTones: [.dark]), + "👩🏿‍🚀": EmojiWithSkinTones(baseEmoji: .femaleAstronaut, skinTones: [.dark]), + "👨🏿‍🚒": EmojiWithSkinTones(baseEmoji: .maleFirefighter, skinTones: [.dark]), + "👩🏿‍🚒": EmojiWithSkinTones(baseEmoji: .femaleFirefighter, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3934(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍🌾": EmojiWithSkinTones(baseEmoji: .farmer, skinTones: [.light]), + "🧑🏼‍🌾": EmojiWithSkinTones(baseEmoji: .farmer, skinTones: [.mediumLight]), + "🧑🏽‍🌾": EmojiWithSkinTones(baseEmoji: .farmer, skinTones: [.medium]), + "🧑🏾‍🌾": EmojiWithSkinTones(baseEmoji: .farmer, skinTones: [.mediumDark]), + "🧑🏿‍🌾": EmojiWithSkinTones(baseEmoji: .farmer, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3935(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍🎓": EmojiWithSkinTones(baseEmoji: .student, skinTones: [.light]), + "🧑🏻‍🍳": EmojiWithSkinTones(baseEmoji: .cook, skinTones: [.light]), + "🧑🏻‍🎤": EmojiWithSkinTones(baseEmoji: .singer, skinTones: [.light]), + "🧑🏻‍🍼": EmojiWithSkinTones(baseEmoji: .personFeedingBaby, skinTones: [.light]), + "🧑🏻‍🎄": EmojiWithSkinTones(baseEmoji: .mxClaus, skinTones: [.light]), + "🧑🏼‍🎓": EmojiWithSkinTones(baseEmoji: .student, skinTones: [.mediumLight]), + "🧑🏼‍🍳": EmojiWithSkinTones(baseEmoji: .cook, skinTones: [.mediumLight]), + "🧑🏼‍🎤": EmojiWithSkinTones(baseEmoji: .singer, skinTones: [.mediumLight]), + "🧑🏼‍🍼": EmojiWithSkinTones(baseEmoji: .personFeedingBaby, skinTones: [.mediumLight]), + "🧑🏼‍🎄": EmojiWithSkinTones(baseEmoji: .mxClaus, skinTones: [.mediumLight]), + "🧑🏽‍🎓": EmojiWithSkinTones(baseEmoji: .student, skinTones: [.medium]), + "🧑🏽‍🍳": EmojiWithSkinTones(baseEmoji: .cook, skinTones: [.medium]), + "🧑🏽‍🎤": EmojiWithSkinTones(baseEmoji: .singer, skinTones: [.medium]), + "🧑🏽‍🍼": EmojiWithSkinTones(baseEmoji: .personFeedingBaby, skinTones: [.medium]), + "🧑🏽‍🎄": EmojiWithSkinTones(baseEmoji: .mxClaus, skinTones: [.medium]), + "🧑🏾‍🎓": EmojiWithSkinTones(baseEmoji: .student, skinTones: [.mediumDark]), + "🧑🏾‍🍳": EmojiWithSkinTones(baseEmoji: .cook, skinTones: [.mediumDark]), + "🧑🏾‍🍼": EmojiWithSkinTones(baseEmoji: .personFeedingBaby, skinTones: [.mediumDark]), + "🧑🏾‍🎄": EmojiWithSkinTones(baseEmoji: .mxClaus, skinTones: [.mediumDark]), + "🧑🏿‍🎓": EmojiWithSkinTones(baseEmoji: .student, skinTones: [.dark]), + "🧑🏿‍🍳": EmojiWithSkinTones(baseEmoji: .cook, skinTones: [.dark]), + "🧑🏿‍🍼": EmojiWithSkinTones(baseEmoji: .personFeedingBaby, skinTones: [.dark]), + "🧑🏿‍🎄": EmojiWithSkinTones(baseEmoji: .mxClaus, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3936(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍🏫": EmojiWithSkinTones(baseEmoji: .teacher, skinTones: [.light]), + "🧑🏻‍🏭": EmojiWithSkinTones(baseEmoji: .factoryWorker, skinTones: [.light]), + "🧑🏻‍🎨": EmojiWithSkinTones(baseEmoji: .artist, skinTones: [.light]), + "🧑🏼‍🏫": EmojiWithSkinTones(baseEmoji: .teacher, skinTones: [.mediumLight]), + "🧑🏼‍🏭": EmojiWithSkinTones(baseEmoji: .factoryWorker, skinTones: [.mediumLight]), + "🧑🏼‍🎨": EmojiWithSkinTones(baseEmoji: .artist, skinTones: [.mediumLight]), + "🧑🏽‍🏫": EmojiWithSkinTones(baseEmoji: .teacher, skinTones: [.medium]), + "🧑🏽‍🏭": EmojiWithSkinTones(baseEmoji: .factoryWorker, skinTones: [.medium]), + "🧑🏽‍🎨": EmojiWithSkinTones(baseEmoji: .artist, skinTones: [.medium]), + "🧑🏾‍🏫": EmojiWithSkinTones(baseEmoji: .teacher, skinTones: [.mediumDark]), + "🧑🏾‍🏭": EmojiWithSkinTones(baseEmoji: .factoryWorker, skinTones: [.mediumDark]), + "🧑🏾‍🎤": EmojiWithSkinTones(baseEmoji: .singer, skinTones: [.mediumDark]), + "🧑🏾‍🎨": EmojiWithSkinTones(baseEmoji: .artist, skinTones: [.mediumDark]), + "🧑🏿‍🏫": EmojiWithSkinTones(baseEmoji: .teacher, skinTones: [.dark]), + "🧑🏿‍🏭": EmojiWithSkinTones(baseEmoji: .factoryWorker, skinTones: [.dark]), + "🧑🏿‍🎤": EmojiWithSkinTones(baseEmoji: .singer, skinTones: [.dark]), + "🧑🏿‍🎨": EmojiWithSkinTones(baseEmoji: .artist, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3937(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨🏻‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedMan, skinTones: [.light]), + "👨🏻‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedMan, skinTones: [.light]), + "👨🏻‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedMan, skinTones: [.light]), + "👨🏻‍🦲": EmojiWithSkinTones(baseEmoji: .baldMan, skinTones: [.light]), + "👩🏻‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedWoman, skinTones: [.light]), + "👩🏻‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedWoman, skinTones: [.light]), + "👩🏻‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedWoman, skinTones: [.light]), + "👩🏻‍🦲": EmojiWithSkinTones(baseEmoji: .baldWoman, skinTones: [.light]), + "👨🏻‍🦯": EmojiWithSkinTones(baseEmoji: .manWithProbingCane, skinTones: [.light]), + "👩🏻‍🦯": EmojiWithSkinTones(baseEmoji: .womanWithProbingCane, skinTones: [.light]), + "👨🏻‍🦼": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchair, skinTones: [.light]), + "👩🏻‍🦼": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchair, skinTones: [.light]), + "👨🏻‍🦽": EmojiWithSkinTones(baseEmoji: .manInManualWheelchair, skinTones: [.light]), + "👩🏻‍🦽": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchair, skinTones: [.light]), + "👨🏼‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedMan, skinTones: [.mediumLight]), + "👨🏼‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedMan, skinTones: [.mediumLight]), + "👨🏼‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedMan, skinTones: [.mediumLight]), + "👨🏼‍🦲": EmojiWithSkinTones(baseEmoji: .baldMan, skinTones: [.mediumLight]), + "👩🏼‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedWoman, skinTones: [.mediumLight]), + "👩🏼‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedWoman, skinTones: [.mediumLight]), + "👩🏼‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedWoman, skinTones: [.mediumLight]), + "👩🏼‍🦲": EmojiWithSkinTones(baseEmoji: .baldWoman, skinTones: [.mediumLight]), + "👨🏼‍🦯": EmojiWithSkinTones(baseEmoji: .manWithProbingCane, skinTones: [.mediumLight]), + "👩🏼‍🦯": EmojiWithSkinTones(baseEmoji: .womanWithProbingCane, skinTones: [.mediumLight]), + "👨🏼‍🦼": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchair, skinTones: [.mediumLight]), + "👩🏼‍🦼": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchair, skinTones: [.mediumLight]), + "👨🏼‍🦽": EmojiWithSkinTones(baseEmoji: .manInManualWheelchair, skinTones: [.mediumLight]), + "👩🏼‍🦽": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchair, skinTones: [.mediumLight]), + "👨🏽‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedMan, skinTones: [.medium]), + "👨🏽‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedMan, skinTones: [.medium]), + "👨🏽‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedMan, skinTones: [.medium]), + "👨🏽‍🦲": EmojiWithSkinTones(baseEmoji: .baldMan, skinTones: [.medium]), + "👩🏽‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedWoman, skinTones: [.medium]), + "👩🏽‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedWoman, skinTones: [.medium]), + "👩🏽‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedWoman, skinTones: [.medium]), + "👩🏽‍🦲": EmojiWithSkinTones(baseEmoji: .baldWoman, skinTones: [.medium]), + "👨🏽‍🦯": EmojiWithSkinTones(baseEmoji: .manWithProbingCane, skinTones: [.medium]), + "👩🏽‍🦯": EmojiWithSkinTones(baseEmoji: .womanWithProbingCane, skinTones: [.medium]), + "👨🏽‍🦼": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchair, skinTones: [.medium]), + "👩🏽‍🦼": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchair, skinTones: [.medium]), + "👨🏽‍🦽": EmojiWithSkinTones(baseEmoji: .manInManualWheelchair, skinTones: [.medium]), + "👩🏽‍🦽": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchair, skinTones: [.medium]), + "👨🏾‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedMan, skinTones: [.mediumDark]), + "👨🏾‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedMan, skinTones: [.mediumDark]), + "👨🏾‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedMan, skinTones: [.mediumDark]), + "👨🏾‍🦲": EmojiWithSkinTones(baseEmoji: .baldMan, skinTones: [.mediumDark]), + "👩🏾‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedWoman, skinTones: [.mediumDark]), + "👩🏾‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedWoman, skinTones: [.mediumDark]), + "👩🏾‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedWoman, skinTones: [.mediumDark]), + "👩🏾‍🦲": EmojiWithSkinTones(baseEmoji: .baldWoman, skinTones: [.mediumDark]), + "👨🏾‍🦯": EmojiWithSkinTones(baseEmoji: .manWithProbingCane, skinTones: [.mediumDark]), + "👩🏾‍🦯": EmojiWithSkinTones(baseEmoji: .womanWithProbingCane, skinTones: [.mediumDark]), + "👨🏾‍🦼": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchair, skinTones: [.mediumDark]), + "👩🏾‍🦼": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchair, skinTones: [.mediumDark]), + "👨🏾‍🦽": EmojiWithSkinTones(baseEmoji: .manInManualWheelchair, skinTones: [.mediumDark]), + "👩🏾‍🦽": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchair, skinTones: [.mediumDark]), + "👨🏿‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedMan, skinTones: [.dark]), + "👨🏿‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedMan, skinTones: [.dark]), + "👨🏿‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedMan, skinTones: [.dark]), + "👨🏿‍🦲": EmojiWithSkinTones(baseEmoji: .baldMan, skinTones: [.dark]), + "👩🏿‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedWoman, skinTones: [.dark]), + "👩🏿‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedWoman, skinTones: [.dark]), + "👩🏿‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedWoman, skinTones: [.dark]), + "👩🏿‍🦲": EmojiWithSkinTones(baseEmoji: .baldWoman, skinTones: [.dark]), + "👨🏿‍🦯": EmojiWithSkinTones(baseEmoji: .manWithProbingCane, skinTones: [.dark]), + "👩🏿‍🦯": EmojiWithSkinTones(baseEmoji: .womanWithProbingCane, skinTones: [.dark]), + "👨🏿‍🦼": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchair, skinTones: [.dark]), + "👩🏿‍🦼": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchair, skinTones: [.dark]), + "👨🏿‍🦽": EmojiWithSkinTones(baseEmoji: .manInManualWheelchair, skinTones: [.dark]), + "👩🏿‍🦽": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchair, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3938(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍💼": EmojiWithSkinTones(baseEmoji: .officeWorker, skinTones: [.light]), + "🧑🏻‍💻": EmojiWithSkinTones(baseEmoji: .technologist, skinTones: [.light]), + "🧑🏼‍💼": EmojiWithSkinTones(baseEmoji: .officeWorker, skinTones: [.mediumLight]), + "🧑🏼‍💻": EmojiWithSkinTones(baseEmoji: .technologist, skinTones: [.mediumLight]), + "🧑🏽‍💼": EmojiWithSkinTones(baseEmoji: .officeWorker, skinTones: [.medium]), + "🧑🏽‍💻": EmojiWithSkinTones(baseEmoji: .technologist, skinTones: [.medium]), + "🧑🏾‍💼": EmojiWithSkinTones(baseEmoji: .officeWorker, skinTones: [.mediumDark]), + "🧑🏾‍💻": EmojiWithSkinTones(baseEmoji: .technologist, skinTones: [.mediumDark]), + "🧑🏿‍💼": EmojiWithSkinTones(baseEmoji: .officeWorker, skinTones: [.dark]), + "🧑🏿‍💻": EmojiWithSkinTones(baseEmoji: .technologist, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3939(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍🔧": EmojiWithSkinTones(baseEmoji: .mechanic, skinTones: [.light]), + "🧑🏻‍🔬": EmojiWithSkinTones(baseEmoji: .scientist, skinTones: [.light]), + "🧑🏼‍🔧": EmojiWithSkinTones(baseEmoji: .mechanic, skinTones: [.mediumLight]), + "🧑🏼‍🔬": EmojiWithSkinTones(baseEmoji: .scientist, skinTones: [.mediumLight]), + "🧑🏽‍🔧": EmojiWithSkinTones(baseEmoji: .mechanic, skinTones: [.medium]), + "🧑🏽‍🔬": EmojiWithSkinTones(baseEmoji: .scientist, skinTones: [.medium]), + "🧑🏾‍🔧": EmojiWithSkinTones(baseEmoji: .mechanic, skinTones: [.mediumDark]), + "🧑🏾‍🔬": EmojiWithSkinTones(baseEmoji: .scientist, skinTones: [.mediumDark]), + "🧑🏿‍🔧": EmojiWithSkinTones(baseEmoji: .mechanic, skinTones: [.dark]), + "🧑🏿‍🔬": EmojiWithSkinTones(baseEmoji: .scientist, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3943(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍🚀": EmojiWithSkinTones(baseEmoji: .astronaut, skinTones: [.light]), + "🧑🏻‍🚒": EmojiWithSkinTones(baseEmoji: .firefighter, skinTones: [.light]), + "🧑🏼‍🚀": EmojiWithSkinTones(baseEmoji: .astronaut, skinTones: [.mediumLight]), + "🧑🏼‍🚒": EmojiWithSkinTones(baseEmoji: .firefighter, skinTones: [.mediumLight]), + "🧑🏽‍🚀": EmojiWithSkinTones(baseEmoji: .astronaut, skinTones: [.medium]), + "🧑🏽‍🚒": EmojiWithSkinTones(baseEmoji: .firefighter, skinTones: [.medium]), + "🧑🏾‍🚀": EmojiWithSkinTones(baseEmoji: .astronaut, skinTones: [.mediumDark]), + "🧑🏾‍🚒": EmojiWithSkinTones(baseEmoji: .firefighter, skinTones: [.mediumDark]), + "🧑🏿‍🚀": EmojiWithSkinTones(baseEmoji: .astronaut, skinTones: [.dark]), + "🧑🏿‍🚒": EmojiWithSkinTones(baseEmoji: .firefighter, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3948(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👁️‍🗨️": EmojiWithSkinTones(baseEmoji: .eyeInSpeechBubble, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3951(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedPerson, skinTones: [.light]), + "🧑🏻‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedPerson, skinTones: [.light]), + "🧑🏻‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedPerson, skinTones: [.light]), + "🧑🏻‍🦲": EmojiWithSkinTones(baseEmoji: .baldPerson, skinTones: [.light]), + "🧑🏻‍🦯": EmojiWithSkinTones(baseEmoji: .personWithProbingCane, skinTones: [.light]), + "🧑🏻‍🦼": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchair, skinTones: [.light]), + "🧑🏻‍🦽": EmojiWithSkinTones(baseEmoji: .personInManualWheelchair, skinTones: [.light]), + "🧑🏼‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedPerson, skinTones: [.mediumLight]), + "🧑🏼‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedPerson, skinTones: [.mediumLight]), + "🧑🏼‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedPerson, skinTones: [.mediumLight]), + "🧑🏼‍🦲": EmojiWithSkinTones(baseEmoji: .baldPerson, skinTones: [.mediumLight]), + "🧑🏼‍🦯": EmojiWithSkinTones(baseEmoji: .personWithProbingCane, skinTones: [.mediumLight]), + "🧑🏼‍🦼": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchair, skinTones: [.mediumLight]), + "🧑🏼‍🦽": EmojiWithSkinTones(baseEmoji: .personInManualWheelchair, skinTones: [.mediumLight]), + "🧑🏽‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedPerson, skinTones: [.medium]), + "🧑🏽‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedPerson, skinTones: [.medium]), + "🧑🏽‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedPerson, skinTones: [.medium]), + "🧑🏽‍🦲": EmojiWithSkinTones(baseEmoji: .baldPerson, skinTones: [.medium]), + "🧑🏽‍🦯": EmojiWithSkinTones(baseEmoji: .personWithProbingCane, skinTones: [.medium]), + "🧑🏽‍🦼": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchair, skinTones: [.medium]), + "🧑🏽‍🦽": EmojiWithSkinTones(baseEmoji: .personInManualWheelchair, skinTones: [.medium]), + "🧑🏾‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedPerson, skinTones: [.mediumDark]), + "🧑🏾‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedPerson, skinTones: [.mediumDark]), + "🧑🏾‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedPerson, skinTones: [.mediumDark]), + "🧑🏾‍🦲": EmojiWithSkinTones(baseEmoji: .baldPerson, skinTones: [.mediumDark]), + "🧑🏾‍🦯": EmojiWithSkinTones(baseEmoji: .personWithProbingCane, skinTones: [.mediumDark]), + "🧑🏾‍🦼": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchair, skinTones: [.mediumDark]), + "🧑🏾‍🦽": EmojiWithSkinTones(baseEmoji: .personInManualWheelchair, skinTones: [.mediumDark]), + "🧑🏿‍🦰": EmojiWithSkinTones(baseEmoji: .redHairedPerson, skinTones: [.dark]), + "🧑🏿‍🦱": EmojiWithSkinTones(baseEmoji: .curlyHairedPerson, skinTones: [.dark]), + "🧑🏿‍🦳": EmojiWithSkinTones(baseEmoji: .whiteHairedPerson, skinTones: [.dark]), + "🧑🏿‍🦲": EmojiWithSkinTones(baseEmoji: .baldPerson, skinTones: [.dark]), + "🧑🏿‍🦯": EmojiWithSkinTones(baseEmoji: .personWithProbingCane, skinTones: [.dark]), + "🧑🏿‍🦼": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchair, skinTones: [.dark]), + "🧑🏿‍🦽": EmojiWithSkinTones(baseEmoji: .personInManualWheelchair, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom4007(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨‍👩‍👦": EmojiWithSkinTones(baseEmoji: .manWomanBoy, skinTones: nil), + "👨‍👩‍👧": EmojiWithSkinTones(baseEmoji: .manWomanGirl, skinTones: nil), + "👨‍👨‍👦": EmojiWithSkinTones(baseEmoji: .manManBoy, skinTones: nil), + "👨‍👨‍👧": EmojiWithSkinTones(baseEmoji: .manManGirl, skinTones: nil), + "👩‍👩‍👦": EmojiWithSkinTones(baseEmoji: .womanWomanBoy, skinTones: nil), + "👩‍👩‍👧": EmojiWithSkinTones(baseEmoji: .womanWomanGirl, skinTones: nil), + "👨‍👦‍👦": EmojiWithSkinTones(baseEmoji: .manBoyBoy, skinTones: nil), + "👨‍👧‍👦": EmojiWithSkinTones(baseEmoji: .manGirlBoy, skinTones: nil), + "👨‍👧‍👧": EmojiWithSkinTones(baseEmoji: .manGirlGirl, skinTones: nil), + "👩‍👦‍👦": EmojiWithSkinTones(baseEmoji: .womanBoyBoy, skinTones: nil), + "👩‍👧‍👦": EmojiWithSkinTones(baseEmoji: .womanGirlBoy, skinTones: nil), + "👩‍👧‍👧": EmojiWithSkinTones(baseEmoji: .womanGirlGirl, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom4046(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍🤝‍🧑": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom4840(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👩‍❤️‍💋‍👨": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: nil), + "👨‍❤️‍💋‍👨": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: nil), + "👩‍❤️‍💋‍👩": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom5237(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🫱🏻‍🫲🏼": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.light, .mediumLight]), + "🫱🏻‍🫲🏽": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.light, .medium]), + "🫱🏻‍🫲🏾": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.light, .mediumDark]), + "🫱🏻‍🫲🏿": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.light, .dark]), + "🫱🏼‍🫲🏻": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.mediumLight, .light]), + "🫱🏼‍🫲🏽": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.mediumLight, .medium]), + "🫱🏼‍🫲🏾": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.mediumLight, .mediumDark]), + "🫱🏼‍🫲🏿": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.mediumLight, .dark]), + "🫱🏽‍🫲🏻": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.medium, .light]), + "🫱🏽‍🫲🏼": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.medium, .mediumLight]), + "🫱🏽‍🫲🏾": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.medium, .mediumDark]), + "🫱🏽‍🫲🏿": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.medium, .dark]), + "🫱🏾‍🫲🏻": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.mediumDark, .light]), + "🫱🏾‍🫲🏼": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.mediumDark, .mediumLight]), + "🫱🏾‍🫲🏽": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.mediumDark, .medium]), + "🫱🏾‍🫲🏿": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.mediumDark, .dark]), + "🫱🏿‍🫲🏻": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.dark, .light]), + "🫱🏿‍🫲🏼": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.dark, .mediumLight]), + "🫱🏿‍🫲🏽": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.dark, .medium]), + "🫱🏿‍🫲🏾": EmojiWithSkinTones(baseEmoji: .handshake, skinTones: [.dark, .mediumDark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom5370(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨‍👩‍👧‍👦": EmojiWithSkinTones(baseEmoji: .manWomanGirlBoy, skinTones: nil), + "👨‍👩‍👦‍👦": EmojiWithSkinTones(baseEmoji: .manWomanBoyBoy, skinTones: nil), + "👨‍👩‍👧‍👧": EmojiWithSkinTones(baseEmoji: .manWomanGirlGirl, skinTones: nil), + "👨‍👨‍👧‍👦": EmojiWithSkinTones(baseEmoji: .manManGirlBoy, skinTones: nil), + "👨‍👨‍👦‍👦": EmojiWithSkinTones(baseEmoji: .manManBoyBoy, skinTones: nil), + "👨‍👨‍👧‍👧": EmojiWithSkinTones(baseEmoji: .manManGirlGirl, skinTones: nil), + "👩‍👩‍👧‍👦": EmojiWithSkinTones(baseEmoji: .womanWomanGirlBoy, skinTones: nil), + "👩‍👩‍👦‍👦": EmojiWithSkinTones(baseEmoji: .womanWomanBoyBoy, skinTones: nil), + "👩‍👩‍👧‍👧": EmojiWithSkinTones(baseEmoji: .womanWomanGirlGirl, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom6037(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👩🏻‍❤️‍👨🏻": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.light]), + "👨🏻‍❤️‍👨🏻": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.light]), + "👩🏻‍❤️‍👩🏻": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.light]), + "👩🏻‍❤️‍👨🏼": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.light, .mediumLight]), + "👨🏻‍❤️‍👨🏼": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.light, .mediumLight]), + "👩🏻‍❤️‍👩🏼": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.light, .mediumLight]), + "👩🏻‍❤️‍👨🏽": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.light, .medium]), + "👨🏻‍❤️‍👨🏽": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.light, .medium]), + "👩🏻‍❤️‍👩🏽": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.light, .medium]), + "👩🏻‍❤️‍👨🏾": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.light, .mediumDark]), + "👨🏻‍❤️‍👨🏾": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.light, .mediumDark]), + "👩🏻‍❤️‍👩🏾": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.light, .mediumDark]), + "👩🏻‍❤️‍👨🏿": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.light, .dark]), + "👨🏻‍❤️‍👨🏿": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.light, .dark]), + "👩🏻‍❤️‍👩🏿": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.light, .dark]), + "👩🏼‍❤️‍👨🏼": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.mediumLight]), + "👨🏼‍❤️‍👨🏼": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.mediumLight]), + "👩🏼‍❤️‍👩🏼": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.mediumLight]), + "👩🏼‍❤️‍👨🏻": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.mediumLight, .light]), + "👨🏼‍❤️‍👨🏻": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.mediumLight, .light]), + "👩🏼‍❤️‍👩🏻": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.mediumLight, .light]), + "👩🏼‍❤️‍👨🏽": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.mediumLight, .medium]), + "👨🏼‍❤️‍👨🏽": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.mediumLight, .medium]), + "👩🏼‍❤️‍👩🏽": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.mediumLight, .medium]), + "👩🏼‍❤️‍👨🏾": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.mediumLight, .mediumDark]), + "👨🏼‍❤️‍👨🏾": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.mediumLight, .mediumDark]), + "👩🏼‍❤️‍👩🏾": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.mediumLight, .mediumDark]), + "👩🏼‍❤️‍👨🏿": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.mediumLight, .dark]), + "👨🏼‍❤️‍👨🏿": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.mediumLight, .dark]), + "👩🏼‍❤️‍👩🏿": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.mediumLight, .dark]), + "👩🏽‍❤️‍👨🏽": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.medium]), + "👨🏽‍❤️‍👨🏽": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.medium]), + "👩🏽‍❤️‍👩🏽": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.medium]), + "👩🏽‍❤️‍👨🏻": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.medium, .light]), + "👨🏽‍❤️‍👨🏻": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.medium, .light]), + "👩🏽‍❤️‍👩🏻": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.medium, .light]), + "👩🏽‍❤️‍👨🏼": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.medium, .mediumLight]), + "👨🏽‍❤️‍👨🏼": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.medium, .mediumLight]), + "👩🏽‍❤️‍👩🏼": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.medium, .mediumLight]), + "👩🏽‍❤️‍👨🏾": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.medium, .mediumDark]), + "👨🏽‍❤️‍👨🏾": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.medium, .mediumDark]), + "👩🏽‍❤️‍👩🏾": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.medium, .mediumDark]), + "👩🏽‍❤️‍👨🏿": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.medium, .dark]), + "👨🏽‍❤️‍👨🏿": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.medium, .dark]), + "👩🏽‍❤️‍👩🏿": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.medium, .dark]), + "👩🏾‍❤️‍👨🏾": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.mediumDark]), + "👨🏾‍❤️‍👨🏾": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.mediumDark]), + "👩🏾‍❤️‍👩🏾": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.mediumDark]), + "👩🏾‍❤️‍👨🏻": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.mediumDark, .light]), + "👨🏾‍❤️‍👨🏻": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.mediumDark, .light]), + "👩🏾‍❤️‍👩🏻": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.mediumDark, .light]), + "👩🏾‍❤️‍👨🏼": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.mediumDark, .mediumLight]), + "👨🏾‍❤️‍👨🏼": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.mediumDark, .mediumLight]), + "👩🏾‍❤️‍👩🏼": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.mediumDark, .mediumLight]), + "👩🏾‍❤️‍👨🏽": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.mediumDark, .medium]), + "👨🏾‍❤️‍👨🏽": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.mediumDark, .medium]), + "👩🏾‍❤️‍👩🏽": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.mediumDark, .medium]), + "👩🏾‍❤️‍👨🏿": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.mediumDark, .dark]), + "👨🏾‍❤️‍👨🏿": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.mediumDark, .dark]), + "👩🏾‍❤️‍👩🏿": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.mediumDark, .dark]), + "👩🏿‍❤️‍👨🏿": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.dark]), + "👨🏿‍❤️‍👨🏿": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.dark]), + "👩🏿‍❤️‍👩🏿": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.dark]), + "👩🏿‍❤️‍👨🏻": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.dark, .light]), + "👨🏿‍❤️‍👨🏻": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.dark, .light]), + "👩🏿‍❤️‍👩🏻": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.dark, .light]), + "👩🏿‍❤️‍👨🏼": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.dark, .mediumLight]), + "👨🏿‍❤️‍👨🏼": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.dark, .mediumLight]), + "👩🏿‍❤️‍👩🏼": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.dark, .mediumLight]), + "👩🏿‍❤️‍👨🏽": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.dark, .medium]), + "👨🏿‍❤️‍👨🏽": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.dark, .medium]), + "👩🏿‍❤️‍👩🏽": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.dark, .medium]), + "👩🏿‍❤️‍👨🏾": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.dark, .mediumDark]), + "👨🏿‍❤️‍👨🏾": EmojiWithSkinTones(baseEmoji: .manHeartMan, skinTones: [.dark, .mediumDark]), + "👩🏿‍❤️‍👩🏾": EmojiWithSkinTones(baseEmoji: .womanHeartWoman, skinTones: [.dark, .mediumDark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom6065(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍❤️‍🧑🏼": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.light, .mediumLight]), + "🧑🏻‍❤️‍🧑🏽": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.light, .medium]), + "🧑🏻‍❤️‍🧑🏾": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.light, .mediumDark]), + "🧑🏻‍❤️‍🧑🏿": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.light, .dark]), + "🧑🏼‍❤️‍🧑🏻": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.mediumLight, .light]), + "🧑🏼‍❤️‍🧑🏽": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.mediumLight, .medium]), + "🧑🏼‍❤️‍🧑🏾": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.mediumLight, .mediumDark]), + "🧑🏼‍❤️‍🧑🏿": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.mediumLight, .dark]), + "🧑🏽‍❤️‍🧑🏻": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.medium, .light]), + "🧑🏽‍❤️‍🧑🏼": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.medium, .mediumLight]), + "🧑🏽‍❤️‍🧑🏾": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.medium, .mediumDark]), + "🧑🏽‍❤️‍🧑🏿": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.medium, .dark]), + "🧑🏾‍❤️‍🧑🏻": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.mediumDark, .light]), + "🧑🏾‍❤️‍🧑🏼": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.mediumDark, .mediumLight]), + "🧑🏾‍❤️‍🧑🏽": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.mediumDark, .medium]), + "🧑🏾‍❤️‍🧑🏿": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.mediumDark, .dark]), + "🧑🏿‍❤️‍🧑🏻": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.dark, .light]), + "🧑🏿‍❤️‍🧑🏼": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.dark, .mediumLight]), + "🧑🏿‍❤️‍🧑🏽": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.dark, .medium]), + "🧑🏿‍❤️‍🧑🏾": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: [.dark, .mediumDark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom6579(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👩🏻‍🤝‍👩🏼": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.light, .mediumLight]), + "👩🏻‍🤝‍👨🏼": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.light, .mediumLight]), + "👨🏻‍🤝‍👨🏼": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.light, .mediumLight]), + "👩🏻‍🤝‍👩🏽": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.light, .medium]), + "👩🏻‍🤝‍👨🏽": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.light, .medium]), + "👨🏻‍🤝‍👨🏽": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.light, .medium]), + "👩🏻‍🤝‍👩🏾": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.light, .mediumDark]), + "👩🏻‍🤝‍👨🏾": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.light, .mediumDark]), + "👨🏻‍🤝‍👨🏾": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.light, .mediumDark]), + "👩🏻‍🤝‍👩🏿": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.light, .dark]), + "👩🏻‍🤝‍👨🏿": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.light, .dark]), + "👨🏻‍🤝‍👨🏿": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.light, .dark]), + "👩🏼‍🤝‍👩🏻": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumLight, .light]), + "👩🏼‍🤝‍👨🏻": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumLight, .light]), + "👨🏼‍🤝‍👨🏻": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumLight, .light]), + "👩🏼‍🤝‍👩🏽": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumLight, .medium]), + "👩🏼‍🤝‍👨🏽": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumLight, .medium]), + "👨🏼‍🤝‍👨🏽": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumLight, .medium]), + "👩🏼‍🤝‍👩🏾": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumLight, .mediumDark]), + "👩🏼‍🤝‍👨🏾": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumLight, .mediumDark]), + "👨🏼‍🤝‍👨🏾": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumLight, .mediumDark]), + "👩🏼‍🤝‍👩🏿": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumLight, .dark]), + "👩🏼‍🤝‍👨🏿": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumLight, .dark]), + "👨🏼‍🤝‍👨🏿": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumLight, .dark]), + "👩🏽‍🤝‍👩🏻": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.medium, .light]), + "👩🏽‍🤝‍👨🏻": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.medium, .light]), + "👨🏽‍🤝‍👨🏻": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.medium, .light]), + "👩🏽‍🤝‍👩🏼": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.medium, .mediumLight]), + "👩🏽‍🤝‍👨🏼": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.medium, .mediumLight]), + "👨🏽‍🤝‍👨🏼": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.medium, .mediumLight]), + "👩🏽‍🤝‍👩🏾": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.medium, .mediumDark]), + "👩🏽‍🤝‍👨🏾": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.medium, .mediumDark]), + "👨🏽‍🤝‍👨🏾": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.medium, .mediumDark]), + "👩🏽‍🤝‍👩🏿": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.medium, .dark]), + "👩🏽‍🤝‍👨🏿": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.medium, .dark]), + "👨🏽‍🤝‍👨🏿": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.medium, .dark]), + "👩🏾‍🤝‍👩🏻": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumDark, .light]), + "👩🏾‍🤝‍👨🏻": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumDark, .light]), + "👨🏾‍🤝‍👨🏻": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumDark, .light]), + "👩🏾‍🤝‍👩🏼": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumDark, .mediumLight]), + "👩🏾‍🤝‍👨🏼": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumDark, .mediumLight]), + "👨🏾‍🤝‍👨🏼": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumDark, .mediumLight]), + "👩🏾‍🤝‍👩🏽": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumDark, .medium]), + "👩🏾‍🤝‍👨🏽": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumDark, .medium]), + "👨🏾‍🤝‍👨🏽": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumDark, .medium]), + "👩🏾‍🤝‍👩🏿": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.mediumDark, .dark]), + "👩🏾‍🤝‍👨🏿": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.mediumDark, .dark]), + "👨🏾‍🤝‍👨🏿": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.mediumDark, .dark]), + "👩🏿‍🤝‍👩🏻": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.dark, .light]), + "👩🏿‍🤝‍👨🏻": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.dark, .light]), + "👨🏿‍🤝‍👨🏻": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.dark, .light]), + "👩🏿‍🤝‍👩🏼": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.dark, .mediumLight]), + "👩🏿‍🤝‍👨🏼": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.dark, .mediumLight]), + "👨🏿‍🤝‍👨🏼": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.dark, .mediumLight]), + "👩🏿‍🤝‍👩🏽": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.dark, .medium]), + "👩🏿‍🤝‍👨🏽": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.dark, .medium]), + "👨🏿‍🤝‍👨🏽": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.dark, .medium]), + "👩🏿‍🤝‍👩🏾": EmojiWithSkinTones(baseEmoji: .twoWomenHoldingHands, skinTones: [.dark, .mediumDark]), + "👩🏿‍🤝‍👨🏾": EmojiWithSkinTones(baseEmoji: .manAndWomanHoldingHands, skinTones: [.dark, .mediumDark]), + "👨🏿‍🤝‍👨🏾": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: [.dark, .mediumDark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom6606(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍🤝‍🧑🏻": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.light]), + "🧑🏻‍🤝‍🧑🏼": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.light, .mediumLight]), + "🧑🏻‍🤝‍🧑🏽": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.light, .medium]), + "🧑🏻‍🤝‍🧑🏾": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.light, .mediumDark]), + "🧑🏻‍🤝‍🧑🏿": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.light, .dark]), + "🧑🏼‍🤝‍🧑🏼": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.mediumLight]), + "🧑🏼‍🤝‍🧑🏻": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.mediumLight, .light]), + "🧑🏼‍🤝‍🧑🏽": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.mediumLight, .medium]), + "🧑🏼‍🤝‍🧑🏾": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.mediumLight, .mediumDark]), + "🧑🏼‍🤝‍🧑🏿": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.mediumLight, .dark]), + "🧑🏽‍🤝‍🧑🏽": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.medium]), + "🧑🏽‍🤝‍🧑🏻": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.medium, .light]), + "🧑🏽‍🤝‍🧑🏼": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.medium, .mediumLight]), + "🧑🏽‍🤝‍🧑🏾": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.medium, .mediumDark]), + "🧑🏽‍🤝‍🧑🏿": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.medium, .dark]), + "🧑🏾‍🤝‍🧑🏾": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.mediumDark]), + "🧑🏾‍🤝‍🧑🏻": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.mediumDark, .light]), + "🧑🏾‍🤝‍🧑🏼": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.mediumDark, .mediumLight]), + "🧑🏾‍🤝‍🧑🏽": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.mediumDark, .medium]), + "🧑🏾‍🤝‍🧑🏿": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.mediumDark, .dark]), + "🧑🏿‍🤝‍🧑🏿": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.dark]), + "🧑🏿‍🤝‍🧑🏻": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.dark, .light]), + "🧑🏿‍🤝‍🧑🏼": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.dark, .mediumLight]), + "🧑🏿‍🤝‍🧑🏽": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.dark, .medium]), + "🧑🏿‍🤝‍🧑🏾": EmojiWithSkinTones(baseEmoji: .peopleHoldingHands, skinTones: [.dark, .mediumDark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom7400(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👩🏻‍❤️‍💋‍👨🏻": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.light]), + "👨🏻‍❤️‍💋‍👨🏻": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.light]), + "👩🏻‍❤️‍💋‍👩🏻": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.light]), + "👩🏻‍❤️‍💋‍👨🏼": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.light, .mediumLight]), + "👨🏻‍❤️‍💋‍👨🏼": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.light, .mediumLight]), + "👩🏻‍❤️‍💋‍👩🏼": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.light, .mediumLight]), + "👩🏻‍❤️‍💋‍👨🏽": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.light, .medium]), + "👨🏻‍❤️‍💋‍👨🏽": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.light, .medium]), + "👩🏻‍❤️‍💋‍👩🏽": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.light, .medium]), + "👩🏻‍❤️‍💋‍👨🏾": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.light, .mediumDark]), + "👨🏻‍❤️‍💋‍👨🏾": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.light, .mediumDark]), + "👩🏻‍❤️‍💋‍👩🏾": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.light, .mediumDark]), + "👩🏻‍❤️‍💋‍👨🏿": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.light, .dark]), + "👨🏻‍❤️‍💋‍👨🏿": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.light, .dark]), + "👩🏻‍❤️‍💋‍👩🏿": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.light, .dark]), + "👩🏼‍❤️‍💋‍👨🏼": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.mediumLight]), + "👨🏼‍❤️‍💋‍👨🏼": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.mediumLight]), + "👩🏼‍❤️‍💋‍👩🏼": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.mediumLight]), + "👩🏼‍❤️‍💋‍👨🏻": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.mediumLight, .light]), + "👨🏼‍❤️‍💋‍👨🏻": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.mediumLight, .light]), + "👩🏼‍❤️‍💋‍👩🏻": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.mediumLight, .light]), + "👩🏼‍❤️‍💋‍👨🏽": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.mediumLight, .medium]), + "👨🏼‍❤️‍💋‍👨🏽": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.mediumLight, .medium]), + "👩🏼‍❤️‍💋‍👩🏽": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.mediumLight, .medium]), + "👩🏼‍❤️‍💋‍👨🏾": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.mediumLight, .mediumDark]), + "👨🏼‍❤️‍💋‍👨🏾": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.mediumLight, .mediumDark]), + "👩🏼‍❤️‍💋‍👩🏾": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.mediumLight, .mediumDark]), + "👩🏼‍❤️‍💋‍👨🏿": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.mediumLight, .dark]), + "👨🏼‍❤️‍💋‍👨🏿": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.mediumLight, .dark]), + "👩🏼‍❤️‍💋‍👩🏿": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.mediumLight, .dark]), + "👩🏽‍❤️‍💋‍👨🏽": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.medium]), + "👨🏽‍❤️‍💋‍👨🏽": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.medium]), + "👩🏽‍❤️‍💋‍👩🏽": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.medium]), + "👩🏽‍❤️‍💋‍👨🏻": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.medium, .light]), + "👨🏽‍❤️‍💋‍👨🏻": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.medium, .light]), + "👩🏽‍❤️‍💋‍👩🏻": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.medium, .light]), + "👩🏽‍❤️‍💋‍👨🏼": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.medium, .mediumLight]), + "👨🏽‍❤️‍💋‍👨🏼": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.medium, .mediumLight]), + "👩🏽‍❤️‍💋‍👩🏼": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.medium, .mediumLight]), + "👩🏽‍❤️‍💋‍👨🏾": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.medium, .mediumDark]), + "👨🏽‍❤️‍💋‍👨🏾": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.medium, .mediumDark]), + "👩🏽‍❤️‍💋‍👩🏾": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.medium, .mediumDark]), + "👩🏽‍❤️‍💋‍👨🏿": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.medium, .dark]), + "👨🏽‍❤️‍💋‍👨🏿": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.medium, .dark]), + "👩🏽‍❤️‍💋‍👩🏿": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.medium, .dark]), + "👩🏾‍❤️‍💋‍👨🏾": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.mediumDark]), + "👨🏾‍❤️‍💋‍👨🏾": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.mediumDark]), + "👩🏾‍❤️‍💋‍👩🏾": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.mediumDark]), + "👩🏾‍❤️‍💋‍👨🏻": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.mediumDark, .light]), + "👨🏾‍❤️‍💋‍👨🏻": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.mediumDark, .light]), + "👩🏾‍❤️‍💋‍👩🏻": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.mediumDark, .light]), + "👩🏾‍❤️‍💋‍👨🏼": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.mediumDark, .mediumLight]), + "👨🏾‍❤️‍💋‍👨🏼": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.mediumDark, .mediumLight]), + "👩🏾‍❤️‍💋‍👩🏼": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.mediumDark, .mediumLight]), + "👩🏾‍❤️‍💋‍👨🏽": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.mediumDark, .medium]), + "👨🏾‍❤️‍💋‍👨🏽": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.mediumDark, .medium]), + "👩🏾‍❤️‍💋‍👩🏽": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.mediumDark, .medium]), + "👩🏾‍❤️‍💋‍👨🏿": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.mediumDark, .dark]), + "👨🏾‍❤️‍💋‍👨🏿": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.mediumDark, .dark]), + "👩🏾‍❤️‍💋‍👩🏿": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.mediumDark, .dark]), + "👩🏿‍❤️‍💋‍👨🏿": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.dark]), + "👨🏿‍❤️‍💋‍👨🏿": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.dark]), + "👩🏿‍❤️‍💋‍👩🏿": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.dark]), + "👩🏿‍❤️‍💋‍👨🏻": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.dark, .light]), + "👨🏿‍❤️‍💋‍👨🏻": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.dark, .light]), + "👩🏿‍❤️‍💋‍👩🏻": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.dark, .light]), + "👩🏿‍❤️‍💋‍👨🏼": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.dark, .mediumLight]), + "👨🏿‍❤️‍💋‍👨🏼": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.dark, .mediumLight]), + "👩🏿‍❤️‍💋‍👩🏼": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.dark, .mediumLight]), + "👩🏿‍❤️‍💋‍👨🏽": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.dark, .medium]), + "👨🏿‍❤️‍💋‍👨🏽": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.dark, .medium]), + "👩🏿‍❤️‍💋‍👩🏽": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.dark, .medium]), + "👩🏿‍❤️‍💋‍👨🏾": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: [.dark, .mediumDark]), + "👨🏿‍❤️‍💋‍👨🏾": EmojiWithSkinTones(baseEmoji: .manKissMan, skinTones: [.dark, .mediumDark]), + "👩🏿‍❤️‍💋‍👩🏾": EmojiWithSkinTones(baseEmoji: .womanKissWoman, skinTones: [.dark, .mediumDark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom7428(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍❤️‍💋‍🧑🏼": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.light, .mediumLight]), + "🧑🏻‍❤️‍💋‍🧑🏽": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.light, .medium]), + "🧑🏻‍❤️‍💋‍🧑🏾": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.light, .mediumDark]), + "🧑🏻‍❤️‍💋‍🧑🏿": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.light, .dark]), + "🧑🏼‍❤️‍💋‍🧑🏻": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.mediumLight, .light]), + "🧑🏼‍❤️‍💋‍🧑🏽": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.mediumLight, .medium]), + "🧑🏼‍❤️‍💋‍🧑🏾": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.mediumLight, .mediumDark]), + "🧑🏼‍❤️‍💋‍🧑🏿": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.mediumLight, .dark]), + "🧑🏽‍❤️‍💋‍🧑🏻": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.medium, .light]), + "🧑🏽‍❤️‍💋‍🧑🏼": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.medium, .mediumLight]), + "🧑🏽‍❤️‍💋‍🧑🏾": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.medium, .mediumDark]), + "🧑🏽‍❤️‍💋‍🧑🏿": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.medium, .dark]), + "🧑🏾‍❤️‍💋‍🧑🏻": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.mediumDark, .light]), + "🧑🏾‍❤️‍💋‍🧑🏼": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.mediumDark, .mediumLight]), + "🧑🏾‍❤️‍💋‍🧑🏽": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.mediumDark, .medium]), + "🧑🏾‍❤️‍💋‍🧑🏿": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.mediumDark, .dark]), + "🧑🏿‍❤️‍💋‍🧑🏻": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.dark, .light]), + "🧑🏿‍❤️‍💋‍🧑🏼": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.dark, .mediumLight]), + "🧑🏿‍❤️‍💋‍🧑🏽": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.dark, .medium]), + "🧑🏿‍❤️‍💋‍🧑🏾": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: [.dark, .mediumDark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom56336(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏴󠁧󠁢󠁥󠁮󠁧󠁿": EmojiWithSkinTones(baseEmoji: .flagEngland, skinTones: nil), + "🏴󠁧󠁢󠁳󠁣󠁴󠁿": EmojiWithSkinTones(baseEmoji: .flagScotland, skinTones: nil), + "🏴󠁧󠁢󠁷󠁬󠁳󠁿": EmojiWithSkinTones(baseEmoji: .flagWales, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } } diff --git a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift index 3dd46b425..58dbe3767 100644 --- a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift +++ b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift @@ -275,14 +275,11 @@ class PhotoCaptureViewController: OWSViewController { let transformFromOrientation: CGAffineTransform switch captureOrientation { - case .portrait: - transformFromOrientation = .identity - case .portraitUpsideDown: - transformFromOrientation = CGAffineTransform(rotationAngle: .pi) - case .landscapeLeft: - transformFromOrientation = CGAffineTransform(rotationAngle: .halfPi) - case .landscapeRight: - transformFromOrientation = CGAffineTransform(rotationAngle: -1 * .halfPi) + case .portrait: transformFromOrientation = .identity + case .portraitUpsideDown: transformFromOrientation = CGAffineTransform(rotationAngle: .pi) + case .landscapeLeft: transformFromOrientation = CGAffineTransform(rotationAngle: .halfPi) + case .landscapeRight: transformFromOrientation = CGAffineTransform(rotationAngle: -1 * .halfPi) + @unknown default: transformFromOrientation = .identity } // Don't "unrotate" the switch camera icon if the front facing camera had been selected. diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index 6645428a5..0b9aa560b 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -59,8 +59,7 @@ public enum PushRegistrationError: Error { .tryFlatMap { _ -> AnyPublisher<(pushToken: String, voipToken: String), Error> in #if targetEnvironment(simulator) throw PushRegistrationError.pushNotSupported(description: "Push not supported on simulators") - #endif - + #else return self.registerForVanillaPushToken() .flatMap { vanillaPushToken -> AnyPublisher<(pushToken: String, voipToken: String), Error> in self.registerForVoipPushToken() @@ -68,6 +67,7 @@ public enum PushRegistrationError: Error { .eraseToAnyPublisher() } .eraseToAnyPublisher() + #endif } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 9db449481..ab99e1080 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -160,9 +160,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { return Deferred { Future { [weak self] resolver in self?.peerConnection?.offer(for: mediaConstraints) { sdp, error in - if let error = error { - return - } + guard error == nil else { return } guard let sdp: RTCSessionDescription = self?.correctSessionDescription(sdp: sdp) else { preconditionFailure() diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index 414c04ba4..fcb375fcf 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -61,7 +61,7 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, public let shouldBeVisible: Bool /// A flag indicating whether the thread is pinned - @available(*, unavailable, message: "use 'pinnedPriority' instead") +// @available(*, unavailable, message: "use 'pinnedPriority' instead") public let isPinned: Bool = false /// The value the user started entering into the input field before they left the conversation screen diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index 72e4ca517..e164c4827 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -165,12 +165,12 @@ public enum SNProtoError: Error { fileprivate class func parseProto(_ proto: SessionProtos_Envelope) throws -> SNProtoEnvelope { guard proto.hasType else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: type") } let type = SNProtoEnvelopeTypeWrap(proto.type) guard proto.hasTimestamp else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: timestamp") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: timestamp") } let timestamp = proto.timestamp @@ -298,12 +298,12 @@ extension SNProtoEnvelope.SNProtoEnvelopeBuilder { fileprivate class func parseProto(_ proto: SessionProtos_TypingMessage) throws -> SNProtoTypingMessage { guard proto.hasTimestamp else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: timestamp") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: timestamp") } let timestamp = proto.timestamp guard proto.hasAction else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: action") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: action") } let action = SNProtoTypingMessageActionWrap(proto.action) @@ -410,12 +410,12 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder { fileprivate class func parseProto(_ proto: SessionProtos_UnsendRequest) throws -> SNProtoUnsendRequest { guard proto.hasTimestamp else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: timestamp") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: timestamp") } let timestamp = proto.timestamp guard proto.hasAuthor else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: author") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: author") } let author = proto.author @@ -541,7 +541,7 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder { fileprivate class func parseProto(_ proto: SessionProtos_MessageRequestResponse) throws -> SNProtoMessageRequestResponse { guard proto.hasIsApproved else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: isApproved") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: isApproved") } let isApproved = proto.isApproved @@ -961,12 +961,12 @@ extension SNProtoContent.SNProtoContentBuilder { fileprivate class func parseProto(_ proto: SessionProtos_CallMessage) throws -> SNProtoCallMessage { guard proto.hasType else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: type") } let type = SNProtoCallMessageTypeWrap(proto.type) guard proto.hasUuid else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: uuid") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: uuid") } let uuid = proto.uuid @@ -1073,12 +1073,12 @@ extension SNProtoCallMessage.SNProtoCallMessageBuilder { fileprivate class func parseProto(_ proto: SessionProtos_KeyPair) throws -> SNProtoKeyPair { guard proto.hasPublicKey else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: publicKey") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: publicKey") } let publicKey = proto.publicKey guard proto.hasPrivateKey else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: privateKey") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: privateKey") } let privateKey = proto.privateKey @@ -1211,7 +1211,7 @@ extension SNProtoKeyPair.SNProtoKeyPairBuilder { fileprivate class func parseProto(_ proto: SessionProtos_DataExtractionNotification) throws -> SNProtoDataExtractionNotification { guard proto.hasType else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: type") } let type = SNProtoDataExtractionNotificationTypeWrap(proto.type) @@ -1620,12 +1620,12 @@ extension SNProtoDataMessageQuoteQuotedAttachment.SNProtoDataMessageQuoteQuotedA fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.Quote) throws -> SNProtoDataMessageQuote { guard proto.hasID else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: id") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: id") } let id = proto.id guard proto.hasAuthor else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: author") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: author") } let author = proto.author @@ -1755,7 +1755,7 @@ extension SNProtoDataMessageQuote.SNProtoDataMessageQuoteBuilder { fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.Preview) throws -> SNProtoDataMessagePreview { guard proto.hasURL else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: url") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: url") } let url = proto.url @@ -1914,17 +1914,17 @@ extension SNProtoDataMessagePreview.SNProtoDataMessagePreviewBuilder { fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.Reaction) throws -> SNProtoDataMessageReaction { guard proto.hasID else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: id") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: id") } let id = proto.id guard proto.hasAuthor else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: author") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: author") } let author = proto.author guard proto.hasAction else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: action") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: action") } let action = SNProtoDataMessageReactionActionWrap(proto.action) @@ -2032,12 +2032,12 @@ extension SNProtoDataMessageReaction.SNProtoDataMessageReactionBuilder { fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.OpenGroupInvitation) throws -> SNProtoDataMessageOpenGroupInvitation { guard proto.hasURL else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: url") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: url") } let url = proto.url guard proto.hasName else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: name") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: name") } let name = proto.name @@ -2144,12 +2144,12 @@ extension SNProtoDataMessageOpenGroupInvitation.SNProtoDataMessageOpenGroupInvit fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.ClosedGroupControlMessage.KeyPairWrapper) throws -> SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper { guard proto.hasPublicKey else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: publicKey") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: publicKey") } let publicKey = proto.publicKey guard proto.hasEncryptedKeyPair else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: encryptedKeyPair") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: encryptedKeyPair") } let encryptedKeyPair = proto.encryptedKeyPair @@ -2387,7 +2387,7 @@ extension SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper.SNProtoDataM fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.ClosedGroupControlMessage) throws -> SNProtoDataMessageClosedGroupControlMessage { guard proto.hasType else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: type") } let type = SNProtoDataMessageClosedGroupControlMessageTypeWrap(proto.type) @@ -3076,12 +3076,12 @@ extension SNProtoConfigurationMessageClosedGroup.SNProtoConfigurationMessageClos fileprivate class func parseProto(_ proto: SessionProtos_ConfigurationMessage.Contact) throws -> SNProtoConfigurationMessageContact { guard proto.hasPublicKey else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: publicKey") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: publicKey") } let publicKey = proto.publicKey guard proto.hasName else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: name") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: name") } let name = proto.name @@ -3396,7 +3396,7 @@ extension SNProtoConfigurationMessage.SNProtoConfigurationMessageBuilder { fileprivate class func parseProto(_ proto: SessionProtos_ReceiptMessage) throws -> SNProtoReceiptMessage { guard proto.hasType else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: type") } let type = SNProtoReceiptMessageTypeWrap(proto.type) @@ -3686,7 +3686,7 @@ extension SNProtoReceiptMessage.SNProtoReceiptMessageBuilder { fileprivate class func parseProto(_ proto: SessionProtos_AttachmentPointer) throws -> SNProtoAttachmentPointer { guard proto.hasID else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: id") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: id") } let id = proto.id @@ -3828,17 +3828,17 @@ extension SNProtoAttachmentPointer.SNProtoAttachmentPointerBuilder { fileprivate class func parseProto(_ proto: SessionProtos_SharedConfigMessage) throws -> SNProtoSharedConfigMessage { guard proto.hasKind else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: kind") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: kind") } let kind = SNProtoSharedConfigMessageKindWrap(proto.kind) guard proto.hasSeqno else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: seqno") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: seqno") } let seqno = proto.seqno guard proto.hasData else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: data") + throw SNProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: data") } let data = proto.data diff --git a/SessionMessagingKit/Protos/Generated/WebSocketProto.swift b/SessionMessagingKit/Protos/Generated/WebSocketProto.swift index c886ccc95..63b9e8dc2 100644 --- a/SessionMessagingKit/Protos/Generated/WebSocketProto.swift +++ b/SessionMessagingKit/Protos/Generated/WebSocketProto.swift @@ -123,17 +123,17 @@ public enum WebSocketProtoError: Error { fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketRequestMessage) throws -> WebSocketProtoWebSocketRequestMessage { guard proto.hasVerb else { - throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: verb") + throw WebSocketProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: verb") } let verb = proto.verb guard proto.hasPath else { - throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: path") + throw WebSocketProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: path") } let path = proto.path guard proto.hasRequestID else { - throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: requestID") + throw WebSocketProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: requestID") } let requestID = proto.requestID @@ -290,12 +290,12 @@ extension WebSocketProtoWebSocketRequestMessage.WebSocketProtoWebSocketRequestMe fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketResponseMessage) throws -> WebSocketProtoWebSocketResponseMessage { guard proto.hasRequestID else { - throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: requestID") + throw WebSocketProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: requestID") } let requestID = proto.requestID guard proto.hasStatus else { - throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: status") + throw WebSocketProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: status") } let status = proto.status @@ -439,7 +439,7 @@ extension WebSocketProtoWebSocketResponseMessage.WebSocketProtoWebSocketResponse fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketMessage) throws -> WebSocketProtoWebSocketMessage { guard proto.hasType else { - throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") + throw WebSocketProtoError.invalidProtobuf(description: "\(String(describing: logTag)) missing required field: type") } let type = WebSocketProtoWebSocketMessageTypeWrap(proto.type) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index 520002125..508d53546 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -88,11 +88,9 @@ extension MessageReceiver { // Need to check if the blinded id matches for open groups switch senderSessionId.prefix { case .blinded15, .blinded25: - let sodium: Sodium = Sodium() - guard let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db), - let blindedKeyPair: KeyPair = try? dependencies.crypto.generate( + let blindedKeyPair: KeyPair = dependencies.crypto.generate( .blindedKeyPair( serverPublicKey: openGroup.publicKey, edKeyPair: userEdKeyPair, diff --git a/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift index df890e31d..96fdfb9d1 100644 --- a/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import SessionUtilitiesKit public extension Notification.Name { static let missedCall = Notification.Name("missedCall") diff --git a/SessionUtilitiesKit/Networking/HTTP.swift b/SessionUtilitiesKit/Networking/HTTP.swift index e22a80af6..d209456c1 100644 --- a/SessionUtilitiesKit/Networking/HTTP.swift +++ b/SessionUtilitiesKit/Networking/HTTP.swift @@ -4,6 +4,11 @@ import Foundation import Combine public enum HTTP { + private struct Certificates { + let isValid: Bool + let certificates: [SecCertificate] + } + private static let seedNodeURLSession = URLSession(configuration: .ephemeral, delegate: seedNodeURLSessionDelegate, delegateQueue: nil) private static let seedNodeURLSessionDelegate = SeedNodeURLSessionDelegateImplementation() private static let snodeURLSession = URLSession(configuration: .ephemeral, delegate: snodeURLSessionDelegate, delegateQueue: nil) @@ -14,22 +19,21 @@ public enum HTTP { /// **Note:** These certificates will need to be regenerated and replaced at the start of April 2025, iOS has a restriction after iOS 13 /// where certificates can have a maximum lifetime of 825 days (https://support.apple.com/en-au/HT210176) as a result we /// can't use the 10 year certificates that the other platforms use - private static let storageSeed1Cert: SecCertificate = { - let path = Bundle.main.path(forResource: "seed1-2023-2y", ofType: "der")! - let data = try! Data(contentsOf: URL(fileURLWithPath: path)) - return SecCertificateCreateWithData(nil, data as CFData)! - }() - - private static let storageSeed2Cert: SecCertificate = { - let path = Bundle.main.path(forResource: "seed2-2023-2y", ofType: "der")! - let data = try! Data(contentsOf: URL(fileURLWithPath: path)) - return SecCertificateCreateWithData(nil, data as CFData)! - }() - - private static let storageSeed3Cert: SecCertificate = { - let path = Bundle.main.path(forResource: "seed3-2023-2y", ofType: "der")! - let data = try! Data(contentsOf: URL(fileURLWithPath: path)) - return SecCertificateCreateWithData(nil, data as CFData)! + private static let storageSeedCertificates: Atomic = { + let certFileNames: [String] = [ + "seed1-2023-2y", + "seed2-2023-2y", + "seed3-2023-2y" + ] + let paths: [String] = certFileNames.compactMap { Bundle.main.path(forResource: $0, ofType: "der") } + let certData: [Data] = paths.compactMap { try? Data(contentsOf: URL(fileURLWithPath: $0)) } + let certificates: [SecCertificate] = certData.compactMap { SecCertificateCreateWithData(nil, $0 as CFData) } + + guard certificates.count == certFileNames.count else { + return Atomic(Certificates(isValid: false, certificates: [])) + } + + return Atomic(Certificates(isValid: true, certificates: certificates)) }() // MARK: - Settings @@ -41,12 +45,15 @@ public enum HTTP { private final class SeedNodeURLSessionDelegateImplementation : NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + guard HTTP.storageSeedCertificates.wrappedValue.isValid else { + SNLog("Failed to set load seed node certificates.") + return completionHandler(.cancelAuthenticationChallenge, nil) + } guard let trust = challenge.protectionSpace.serverTrust else { return completionHandler(.cancelAuthenticationChallenge, nil) } // Mark the seed node certificates as trusted - let certificates = [ storageSeed1Cert, storageSeed2Cert, storageSeed3Cert ] - guard SecTrustSetAnchorCertificates(trust, certificates as CFArray) == errSecSuccess else { + guard SecTrustSetAnchorCertificates(trust, HTTP.storageSeedCertificates.wrappedValue.certificates as CFArray) == errSecSuccess else { SNLog("Failed to set seed node certificates.") return completionHandler(.cancelAuthenticationChallenge, nil) } From cdf918194a63f70bb76f8c8f973d11966b3f24a4 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 21 Sep 2023 13:43:11 +1000 Subject: [PATCH 07/12] Fixed an issue where XCode 15 would hang on launch due to being unable to retrieve a read lock --- SessionUtilitiesKit/General/Atomic.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/SessionUtilitiesKit/General/Atomic.swift b/SessionUtilitiesKit/General/Atomic.swift index d16ac102c..8d3ebf45a 100644 --- a/SessionUtilitiesKit/General/Atomic.swift +++ b/SessionUtilitiesKit/General/Atomic.swift @@ -81,11 +81,14 @@ extension Atomic where Value: CustomDebugStringConvertible { // MARK: - ReadWriteLock private class ReadWriteLock { - private var rwlock: pthread_rwlock_t = { - var rwlock = pthread_rwlock_t() + private var rwlock: pthread_rwlock_t + + // Need to do this in a proper init function instead of a lazy variable or it can indefinitely + // hang on XCode 15 when trying to retrieve a lock (potentially due to optimisations?) + init() { + rwlock = pthread_rwlock_t() pthread_rwlock_init(&rwlock, nil) - return rwlock - }() + } func writeLock() { pthread_rwlock_wrlock(&rwlock) From 52836cff91effd34191f6803eefb2f980bf4d426 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 21 Sep 2023 15:55:26 +1000 Subject: [PATCH 08/12] Fixed a couple more issues Fixed an issue with the Emoji generation Fixed the SessionThread 'isPinned' property Fixed an issue when migrating from a pre 2.3.0 version to the latest version --- Scripts/EmojiGenerator.swift | 8 ++++---- Session/Emoji/EmojiWithSkinTones+String.swift | 2 +- Session/Meta/SessionApp.swift | 2 +- Session/Utilities/MockDataGenerator.swift | 9 +++------ .../Migrations/_001_InitialSetupMigration.swift | 2 +- .../Database/Migrations/_003_YDBToGRDBMigration.swift | 9 +++------ .../Database/Migrations/_013_SessionUtilChanges.swift | 2 +- SessionMessagingKit/Database/Models/Profile.swift | 10 +++++----- .../Database/Models/SessionThread.swift | 6 +++--- .../Config Handling/SessionUtil+Contacts.swift | 3 +-- SessionMessagingKit/Utilities/ProfileManager.swift | 2 +- .../Settings/ThreadSettingsViewModelSpec.swift | 6 ++---- .../Style Guide/Themes/Theme+ClassicDark.swift | 8 ++++---- .../Style Guide/Themes/Theme+ClassicLight.swift | 2 +- SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift | 4 ++-- .../Database/Types/TypedTableDefinition.swift | 4 ++++ 16 files changed, 37 insertions(+), 42 deletions(-) diff --git a/Scripts/EmojiGenerator.swift b/Scripts/EmojiGenerator.swift index 58e1cda55..680d86e5d 100755 --- a/Scripts/EmojiGenerator.swift +++ b/Scripts/EmojiGenerator.swift @@ -279,10 +279,10 @@ extension EmojiGenerator { } } - func switchString(with variableName: String = "rawValue") -> String { + func switchString(with variableName: String = "rawValue", size: UInt32) -> String { switch self { - case .firstScalar: return "rawValue.unicodeScalars.map({ $0.value }).first" - case .scalarSum: return "rawValue.unicodeScalars.map({ $0.value }).reduce(0, +)" + case .firstScalar: return "rawValue.unicodeScalars.map({ $0.value }).first.map({ $0 / \(size) })" + case .scalarSum: return "(rawValue.unicodeScalars.map({ $0.value }).reduce(0, +) / \(size))" } } } @@ -323,7 +323,7 @@ extension EmojiGenerator { fileHandle.writeLine("init?(rawValue: String) {") fileHandle.indent { fileHandle.writeLine("guard rawValue.isSingleEmoji else { return nil }") - fileHandle.writeLine("switch \(chunkType.switchString()) {") + fileHandle.writeLine("switch \(chunkType.switchString(size: chunkSize)) {") fileHandle.indent { chunkedEmojiInfo.forEach { chunk, _ in fileHandle.writeLine("case \(chunk): self = EmojiWithSkinTones.emojiFrom\(chunk)(rawValue)") diff --git a/Session/Emoji/EmojiWithSkinTones+String.swift b/Session/Emoji/EmojiWithSkinTones+String.swift index 8f4bd5d74..c57a6cba3 100644 --- a/Session/Emoji/EmojiWithSkinTones+String.swift +++ b/Session/Emoji/EmojiWithSkinTones+String.swift @@ -4,7 +4,7 @@ extension EmojiWithSkinTones { init?(rawValue: String) { guard rawValue.isSingleEmoji else { return nil } - switch rawValue.unicodeScalars.map({ $0.value }).reduce(0, +) { + switch (rawValue.unicodeScalars.map({ $0.value }).reduce(0, +) / 100) { case 89: self = EmojiWithSkinTones.emojiFrom89(rawValue) case 91: self = EmojiWithSkinTones.emojiFrom91(rawValue) case 92: self = EmojiWithSkinTones.emojiFrom92(rawValue) diff --git a/Session/Meta/SessionApp.swift b/Session/Meta/SessionApp.swift index 7ffa9292c..94f52ae67 100644 --- a/Session/Meta/SessionApp.swift +++ b/Session/Meta/SessionApp.swift @@ -82,7 +82,7 @@ public struct SessionApp { ) } - /// The thread should generally exist at the time of calling this method, but on the off change it doesn't then we need to `fetchOrCreate` it and + /// The thread should generally exist at the time of calling this method, but on the off chance it doesn't then we need to `fetchOrCreate` it and /// should do it on a background thread just in case something is keeping the DBWrite thread busy as in the past this could cause the app to hang guard threadInfo?.threadExists == true else { DispatchQueue.global(qos: .userInitiated).async { diff --git a/Session/Utilities/MockDataGenerator.swift b/Session/Utilities/MockDataGenerator.swift index e41c9be22..b268786c3 100644 --- a/Session/Utilities/MockDataGenerator.swift +++ b/Session/Utilities/MockDataGenerator.swift @@ -100,8 +100,7 @@ enum MockDataGenerator { .compactMap { _ in stringContent.randomElement(using: &dmThreadRandomGenerator) } .joined(), lastNameUpdate: Date().timeIntervalSince1970, - lastProfilePictureUpdate: Date().timeIntervalSince1970, - lastBlocksCommunityMessageRequests: 0 + lastProfilePictureUpdate: Date().timeIntervalSince1970 ) .saved(db) @@ -182,8 +181,7 @@ enum MockDataGenerator { .compactMap { _ in stringContent.randomElement(using: &cgThreadRandomGenerator) } .joined(), lastNameUpdate: Date().timeIntervalSince1970, - lastProfilePictureUpdate: Date().timeIntervalSince1970, - lastBlocksCommunityMessageRequests: 0 + lastProfilePictureUpdate: Date().timeIntervalSince1970 ) .saved(db) @@ -313,8 +311,7 @@ enum MockDataGenerator { .compactMap { _ in stringContent.randomElement(using: &ogThreadRandomGenerator) } .joined(), lastNameUpdate: Date().timeIntervalSince1970, - lastProfilePictureUpdate: Date().timeIntervalSince1970, - lastBlocksCommunityMessageRequests: 0 + lastProfilePictureUpdate: Date().timeIntervalSince1970 ) .saved(db) diff --git a/SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift b/SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift index a553a5d6d..b140a8d20 100644 --- a/SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift @@ -65,7 +65,7 @@ enum _001_InitialSetupMigration: Migration { t.column(.variant, .integer).notNull() t.column(.creationDateTimestamp, .double).notNull() t.column(.shouldBeVisible, .boolean).notNull() - t.column(.isPinned, .boolean).notNull() + t.deprecatedColumn(name: "isPinned", .boolean).notNull() t.column(.messageDraft, .text) t.column(.notificationSound, .integer) t.column(.mutedUntilTimestamp, .double) diff --git a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift index d7ac2eabf..4873cb107 100644 --- a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -422,8 +422,7 @@ enum _003_YDBToGRDBMigration: Migration { profilePictureUrl: legacyContact.profilePictureURL, profilePictureFileName: legacyContact.profilePictureFileName, profileEncryptionKey: legacyContact.profileEncryptionKey?.keyData, - lastProfilePictureUpdate: 0, - lastBlocksCommunityMessageRequests: 0 + lastProfilePictureUpdate: 0 ).migrationSafeInsert(db) /// **Note:** The blow "shouldForce" flags are here to allow us to avoid having to run legacy migrations they @@ -646,8 +645,7 @@ enum _003_YDBToGRDBMigration: Migration { id: profileId, name: profileId, lastNameUpdate: 0, - lastProfilePictureUpdate: 0, - lastBlocksCommunityMessageRequests: 0 + lastProfilePictureUpdate: 0 ).migrationSafeSave(db) } @@ -1061,8 +1059,7 @@ enum _003_YDBToGRDBMigration: Migration { id: quotedMessage.authorId, name: quotedMessage.authorId, lastNameUpdate: 0, - lastProfilePictureUpdate: 0, - lastBlocksCommunityMessageRequests: 0 + lastProfilePictureUpdate: 0 ).migrationSafeSave(db) } diff --git a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift index f70169fca..8f7ae09f8 100644 --- a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift @@ -180,7 +180,7 @@ enum _013_SessionUtilChanges: Migration { // Migrate the 'isPinned' value to 'pinnedPriority' try SessionThread - .filter(SessionThread.Columns.isPinned == true) + .filter(sql: "isPinned = true") .updateAll( db, SessionThread.Columns.pinnedPriority.set(to: 1) diff --git a/SessionMessagingKit/Database/Models/Profile.swift b/SessionMessagingKit/Database/Models/Profile.swift index 7b8695929..333f17ff9 100644 --- a/SessionMessagingKit/Database/Models/Profile.swift +++ b/SessionMessagingKit/Database/Models/Profile.swift @@ -60,7 +60,7 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco public let blocksCommunityMessageRequests: Bool? /// The timestamp (in seconds since epoch) that the `blocksCommunityMessageRequests` setting was last updated - public let lastBlocksCommunityMessageRequests: TimeInterval + public let lastBlocksCommunityMessageRequests: TimeInterval? // MARK: - Initialization @@ -74,7 +74,7 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco profileEncryptionKey: Data? = nil, lastProfilePictureUpdate: TimeInterval, blocksCommunityMessageRequests: Bool? = nil, - lastBlocksCommunityMessageRequests: TimeInterval + lastBlocksCommunityMessageRequests: TimeInterval? = nil ) { self.id = id self.name = name @@ -129,7 +129,7 @@ public extension Profile { profileEncryptionKey: profileKey, lastProfilePictureUpdate: try container.decode(TimeInterval.self, forKey: .lastProfilePictureUpdate), blocksCommunityMessageRequests: try? container.decode(Bool.self, forKey: .blocksCommunityMessageRequests), - lastBlocksCommunityMessageRequests: try container.decode(TimeInterval.self, forKey: .lastBlocksCommunityMessageRequests) + lastBlocksCommunityMessageRequests: try? container.decode(TimeInterval.self, forKey: .lastBlocksCommunityMessageRequests) ) } @@ -145,7 +145,7 @@ public extension Profile { try container.encodeIfPresent(profileEncryptionKey, forKey: .profileEncryptionKey) try container.encode(lastProfilePictureUpdate, forKey: .lastProfilePictureUpdate) try container.encodeIfPresent(blocksCommunityMessageRequests, forKey: .blocksCommunityMessageRequests) - try container.encode(lastBlocksCommunityMessageRequests, forKey: .lastBlocksCommunityMessageRequests) + try container.encodeIfPresent(lastBlocksCommunityMessageRequests, forKey: .lastBlocksCommunityMessageRequests) } } @@ -263,7 +263,7 @@ public extension Profile { profileEncryptionKey: nil, lastProfilePictureUpdate: 0, blocksCommunityMessageRequests: nil, - lastBlocksCommunityMessageRequests: 0 + lastBlocksCommunityMessageRequests: nil ) } diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index fcb375fcf..699eb0786 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -27,7 +27,7 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, case variant case creationDateTimestamp case shouldBeVisible - case isPinned + @available(*, deprecated, message: "use 'pinnedPriority > 0' instead") case isPinned case messageDraft case notificationSound case mutedUntilTimestamp @@ -61,8 +61,8 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, public let shouldBeVisible: Bool /// A flag indicating whether the thread is pinned -// @available(*, unavailable, message: "use 'pinnedPriority' instead") - public let isPinned: Bool = false + @available(*, deprecated, message: "use 'pinnedPriority > 0' instead") + private let isPinned: Bool = false /// The value the user started entering into the input field before they left the conversation screen public let messageDraft: String? diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift index 745373060..4e07e7aad 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -564,8 +564,7 @@ private extension SessionUtil { count: ProfileManager.avatarAES256KeyByteLength ) ), - lastProfilePictureUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000), - lastBlocksCommunityMessageRequests: 0 + lastProfilePictureUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000) ) result[contactId] = ContactData( diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 1a1488984..7c206107d 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -517,7 +517,7 @@ public struct ProfileManager { } // Blocks community message requets flag - if let blocksCommunityMessageRequests: Bool = blocksCommunityMessageRequests, sentTimestamp > profile.lastBlocksCommunityMessageRequests { + if let blocksCommunityMessageRequests: Bool = blocksCommunityMessageRequests, sentTimestamp > (profile.lastBlocksCommunityMessageRequests ?? 0) { profileChanges.append(Profile.Columns.blocksCommunityMessageRequests.set(to: blocksCommunityMessageRequests)) profileChanges.append(Profile.Columns.lastBlocksCommunityMessageRequests.set(to: sentTimestamp)) } diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index 8ba49032b..e84e52368 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -61,16 +61,14 @@ class ThreadSettingsViewModelSpec: QuickSpec { id: "05\(TestConstants.publicKey)", name: "TestMe", lastNameUpdate: 0, - lastProfilePictureUpdate: 0, - lastBlocksCommunityMessageRequests: 0 + lastProfilePictureUpdate: 0 ).insert(db) try Profile( id: "TestId", name: "TestUser", lastNameUpdate: 0, - lastProfilePictureUpdate: 0, - lastBlocksCommunityMessageRequests: 0 + lastProfilePictureUpdate: 0 ).insert(db) } viewModel = ThreadSettingsViewModel( diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift index 1bd0e039a..468356fc9 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift @@ -82,11 +82,11 @@ internal enum Theme_ClassicDark: ThemeColors { .alert_buttonBackground: .classicDark1, // ConversationButton - .conversationButton_background: .classicDark1, - .conversationButton_unreadBackground: .classicDark2, + .conversationButton_background: .classicDark0, + .conversationButton_unreadBackground: .classicDark1, .conversationButton_unreadStripBackground: .primary, - .conversationButton_unreadBubbleBackground: .classicDark3, - .conversationButton_unreadBubbleText: .classicDark6, + .conversationButton_unreadBubbleBackground: .primary, + .conversationButton_unreadBubbleText: .classicDark0, .conversationButton_swipeDestructive: .dangerDark, .conversationButton_swipeSecondary: .classicDark2, .conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color, diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift index b659a95e0..51caf802f 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift @@ -85,7 +85,7 @@ internal enum Theme_ClassicLight: ThemeColors { .conversationButton_background: .classicLight6, .conversationButton_unreadBackground: .classicLight6, .conversationButton_unreadStripBackground: .primary, - .conversationButton_unreadBubbleBackground: .classicLight3, + .conversationButton_unreadBubbleBackground: .primary, .conversationButton_unreadBubbleText: .classicLight0, .conversationButton_swipeDestructive: .dangerLight, .conversationButton_swipeSecondary: .classicLight1, diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift index a87cf4d4d..23a15bc8e 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift @@ -82,8 +82,8 @@ internal enum Theme_OceanDark: ThemeColors { .alert_buttonBackground: .oceanDark3, // ConversationButton - .conversationButton_background: .oceanDark3, - .conversationButton_unreadBackground: .oceanDark4, + .conversationButton_background: .oceanDark2, + .conversationButton_unreadBackground: .oceanDark3, .conversationButton_unreadStripBackground: .primary, .conversationButton_unreadBubbleBackground: .primary, .conversationButton_unreadBubbleText: .oceanDark0, diff --git a/SessionUtilitiesKit/Database/Types/TypedTableDefinition.swift b/SessionUtilitiesKit/Database/Types/TypedTableDefinition.swift index 67ce68016..30ed50425 100644 --- a/SessionUtilitiesKit/Database/Types/TypedTableDefinition.swift +++ b/SessionUtilitiesKit/Database/Types/TypedTableDefinition.swift @@ -16,6 +16,10 @@ public class TypedTableDefinition where T: TableRecord, T: ColumnExpressible return definition.column(key.name, type) } + @discardableResult public func deprecatedColumn(name: String, _ type: Database.ColumnType? = nil) -> ColumnDefinition { + return definition.column(name, type) + } + public func primaryKey(_ columns: [T.Columns], onConflict: Database.ConflictResolution? = nil) { definition.primaryKey(columns.map { $0.name }, onConflict: onConflict) } From 8fee4edf3496591a3657c5341269e4a8d243d522 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 22 Sep 2023 09:28:51 +1000 Subject: [PATCH 09/12] Updated to the latest translations from Crowdin --- Session.xcodeproj/project.pbxproj | 192 +++-- .../ExpandingAttachmentsButton.swift | 3 + .../Translations/ar.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/be.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/bg.lproj/Localizable.strings | 783 ++++++++++++++++++ Session/Meta/Translations/bin/auto-genstrings | 55 -- .../Meta/Translations/bin/pull-translations | 46 - .../Translations/bin/push-translation-source | 11 - .../Meta/Translations/bin/sync-translations | 35 - .../Translations/bn.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/cs.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/da.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/de.lproj/Localizable.strings | 621 ++++++++------ .../Translations/el.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/en.lproj/Localizable.strings | 46 +- .../Translations/eo.lproj/Localizable.strings | 782 +++++++++++++++++ .../Localizable.strings | 546 +++++++----- .../Translations/fa.lproj/Localizable.strings | 215 ++++- .../Translations/fi.lproj/Localizable.strings | 633 ++++++++------ .../Localizable.strings | 383 ++++++--- .../Translations/fr.lproj/Localizable.strings | 525 +++++++----- .../Translations/hi.lproj/Localizable.strings | 191 ++++- .../Translations/hr.lproj/Localizable.strings | 251 ++++-- .../Translations/hu.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/id.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/it.lproj/Localizable.strings | 491 +++++++---- .../Translations/ja.lproj/Localizable.strings | 481 +++++++---- .../Translations/ko.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/ku.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/lt.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/lv.lproj/Localizable.strings | 782 +++++++++++++++++ .../ne-NP.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/nl.lproj/Localizable.strings | 265 ++++-- .../Translations/no.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/pl.lproj/Localizable.strings | 467 +++++++---- .../Localizable.strings | 521 +++++++----- .../pt-PT.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/ro.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/ru.lproj/Localizable.strings | 505 ++++++----- .../si-LK.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/sk.lproj/Localizable.strings | 503 ++++++----- .../Localizable.strings | 579 ++++++++----- .../Localizable.strings | 413 +++++---- .../Translations/th.lproj/Localizable.strings | 229 +++-- .../Translations/tr.lproj/Localizable.strings | 782 +++++++++++++++++ .../Translations/uk.lproj/Localizable.strings | 217 ++++- .../Localizable.strings | 187 ++++- .../Localizable.strings | 473 +++++++---- .../Localizable.strings | 421 ++++++---- 49 files changed, 21790 insertions(+), 3356 deletions(-) create mode 100644 Session/Meta/Translations/ar.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/be.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/bg.lproj/Localizable.strings delete mode 100755 Session/Meta/Translations/bin/auto-genstrings delete mode 100755 Session/Meta/Translations/bin/pull-translations delete mode 100755 Session/Meta/Translations/bin/push-translation-source delete mode 100755 Session/Meta/Translations/bin/sync-translations create mode 100644 Session/Meta/Translations/bn.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/cs.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/da.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/el.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/eo.lproj/Localizable.strings rename Session/Meta/Translations/{es.lproj => es-ES.lproj}/Localizable.strings (62%) rename Session/Meta/Translations/{si.lproj => fil.lproj}/Localizable.strings (78%) create mode 100644 Session/Meta/Translations/hu.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/id.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/ko.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/ku.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/lt.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/lv.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/ne-NP.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/no.lproj/Localizable.strings rename Session/Meta/Translations/{pt_BR.lproj => pt-BR.lproj}/Localizable.strings (63%) create mode 100644 Session/Meta/Translations/pt-PT.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/ro.lproj/Localizable.strings create mode 100644 Session/Meta/Translations/si-LK.lproj/Localizable.strings rename Session/Meta/Translations/{id-ID.lproj => sl.lproj}/Localizable.strings (63%) rename Session/Meta/Translations/{sv.lproj => sv-SE.lproj}/Localizable.strings (74%) create mode 100644 Session/Meta/Translations/tr.lproj/Localizable.strings rename Session/Meta/Translations/{vi-VN.lproj => vi.lproj}/Localizable.strings (88%) rename Session/Meta/Translations/{zh_CN.lproj => zh-CN.lproj}/Localizable.strings (67%) rename Session/Meta/Translations/{zh-Hant.lproj => zh-TW.lproj}/Localizable.strings (74%) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 48ee04b36..9ba235460 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -12,7 +12,6 @@ 34661FB820C1C0D60056EDD6 /* message_sent.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 34661FB720C1C0D60056EDD6 /* message_sent.aiff */; }; 346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */; }; 3478504C1FD7496D007B8332 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B66DBF4919D5BBC8006EA940 /* Images.xcassets */; }; - 347850551FD749C0007B8332 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; 3488F9362191CC4000E524CC /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3488F9352191CC4000E524CC /* MediaView.swift */; }; 3496955C219B605E00DCFE74 /* ImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34969559219B605E00DCFE74 /* ImagePickerController.swift */; }; 3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496955B219B605E00DCFE74 /* PhotoLibrary.swift */; }; @@ -156,7 +155,6 @@ 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; }; 7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD477A727EC39F5004E2822 /* Atomic.swift */; }; 7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; }; - 7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; 7BFA8AE32831D0D4001876F3 /* ContextMenuVC+EmojiReactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFA8AE22831D0D4001876F3 /* ContextMenuVC+EmojiReactsView.swift */; }; 7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A892745C4F000FB91B9 /* Permissions.swift */; }; 7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.swift */; }; @@ -170,7 +168,6 @@ B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B66DBF4919D5BBC8006EA940 /* Images.xcassets */; }; B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B67EBF5C19194AC60084CCFD /* Settings.bundle */; }; B6B226971BE4B7D200860F4D /* ContactsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6B226961BE4B7D200860F4D /* ContactsUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - B6F509971AA53F760068F56A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */; }; B7ED5A721C85869B379140C0 /* Pods_GlobalDependencies_Session.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1677131E9189A0043FF97ACB /* Pods_GlobalDependencies_Session.framework */; }; B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */; }; @@ -736,6 +733,7 @@ FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */; }; FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7432804EF1B004C14C5 /* JobRunner.swift */; }; FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */; }; + FD9401D12ABD04AC003A4834 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FD9401A32ABD04AC003A4834 /* Localizable.strings */; }; FD96F3A529DBC3DC00401309 /* MessageSendJobSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */; }; FD96F3A729DBD43D00401309 /* MockJobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A629DBD43D00401309 /* MockJobRunner.swift */; }; FD97B2402A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */; }; @@ -1184,7 +1182,6 @@ 45F32C1D205718B000A300D5 /* MediaPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MediaPageViewController.swift; path = "Session/Media Viewing & Editing/MediaPageViewController.swift"; sourceTree = SOURCE_ROOT; }; 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = ""; }; 4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoGridViewCell.swift; sourceTree = ""; }; - 4C1D2337218B6BA000A0598F /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; 4C21D5D7223AC60F00EF8A77 /* PhotoCapture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCapture.swift; sourceTree = ""; }; 4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMediaNavigationController.swift; sourceTree = ""; }; 4C586924224FAB83003FD070 /* AVAudioSession+OWS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AVAudioSession+OWS.h"; sourceTree = ""; }; @@ -1218,7 +1215,6 @@ 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timer+MainThread.swift"; sourceTree = ""; }; 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaInfoView.swift"; sourceTree = ""; }; 7B2561C329874851005C086C /* SessionCarouselView+Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCarouselView+Info.swift"; sourceTree = ""; }; - 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoVC.swift; sourceTree = ""; }; 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaPreviewView.swift"; sourceTree = ""; }; 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCarouselView.swift; sourceTree = ""; }; @@ -1291,15 +1287,9 @@ B1910A32EB2AD01913629646 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig"; sourceTree = ""; }; B4F9FCBDA07F07CB48220D4C /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig"; sourceTree = ""; }; B60EDE031A05A01700D73516 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; - B646D10E1AA5461A004133BA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; B657DDC91911A40500F45B0C /* Signal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Signal.entitlements; sourceTree = ""; }; B66DBF4919D5BBC8006EA940 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; - B676BCEF1AA544E7009637B8 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; - B676BCF11AA5451E009637B8 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; B67EBF5C19194AC60084CCFD /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = Settings.bundle; path = Session/Meta/Settings.bundle; sourceTree = SOURCE_ROOT; }; - B68CB7DC1AA547100065AC3F /* pt_BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt_BR; path = pt_BR.lproj/Localizable.strings; sourceTree = ""; }; - B68CB7E01AA548420065AC3F /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; - B68CB7E61AA548870065AC3F /* zh_CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_CN; path = zh_CN.lproj/Localizable.strings; sourceTree = ""; }; B6B226961BE4B7D200860F4D /* ContactsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ContactsUI.framework; path = System/Library/Frameworks/ContactsUI.framework; sourceTree = SDKROOT; }; B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; }; B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderView.swift; sourceTree = ""; }; @@ -1317,7 +1307,6 @@ B82B408B239A068800A248E7 /* RegisterVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterVC.swift; sourceTree = ""; }; B82B408D239DC00D00A248E7 /* DisplayNameVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayNameVC.swift; sourceTree = ""; }; B82B408F239DD75000A248E7 /* RestoreVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreVC.swift; sourceTree = ""; }; - B834C6DD26533AE5001091B2 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; B835246D25C38ABF0089A44F /* ConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationVC.swift; sourceTree = ""; }; B835247825C38D880089A44F /* MessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = ""; }; B835249A25C3AB650089A44F /* VisibleMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleMessageCell.swift; sourceTree = ""; }; @@ -1333,7 +1322,6 @@ B8569AE225CBB19A00DBA3DB /* DocumentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentView.swift; sourceTree = ""; }; B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = ""; }; B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = ""; }; - B87588582644CA9D000E60D0 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; B877E24126CA12910007970A /* CallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVC.swift; sourceTree = ""; }; B877E24526CA13BA0007970A /* CallVC+Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVC+Camera.swift"; sourceTree = ""; }; B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = ""; }; @@ -1354,9 +1342,6 @@ B8B558F026C4BB0600693325 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; }; B8B558FE26C4E05E00693325 /* WebRTCSession+MessageHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+MessageHandling.swift"; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* SessionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionButton.swift; sourceTree = ""; }; - B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; - B8BAC75C2695648500EA1759 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; - B8BAC75D2695649000EA1759 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = ""; }; B8BB82A1238F356100BA5194 /* Values.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Values.swift; sourceTree = ""; }; B8BB82A4238F627000BA5194 /* HomeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeVC.swift; sourceTree = ""; }; B8BB82AA238F669C00BA5194 /* FullConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullConversationCell.swift; sourceTree = ""; }; @@ -1379,7 +1364,6 @@ B8D84ECE25E3108A005A043E /* ExpandingAttachmentsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandingAttachmentsButton.swift; sourceTree = ""; }; B8DE1FB326C22F2F0079C9CE /* WebRTCSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCSession.swift; sourceTree = ""; }; B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessage.swift; sourceTree = ""; }; - B8EB20E6263F7E4B00773E52 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+OpenGroupInvitation.swift"; sourceTree = ""; }; B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupInvitationView.swift; sourceTree = ""; }; B8F5F58225EC94A6003BF8D4 /* Collection+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Utilities.swift"; sourceTree = ""; }; @@ -1543,20 +1527,13 @@ C38EF3E9255B6DF6007E1867 /* Toast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Toast.swift; path = "SignalUtilitiesKit/Shared Views/Toast.swift"; sourceTree = SOURCE_ROOT; }; C38EF3ED255B6DF6007E1867 /* TappableStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TappableStackView.swift; path = "SignalUtilitiesKit/Shared Views/TappableStackView.swift"; sourceTree = SOURCE_ROOT; }; C38EF3EE255B6DF6007E1867 /* GradientView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GradientView.swift; path = SessionUIKit/Components/GradientView.swift; sourceTree = SOURCE_ROOT; }; - C396469C2509D3ED00B0B9F5 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; - C396469D2509D3F400B0B9F5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; - C396469E2509D40400B0B9F5 /* vi-VN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "vi-VN"; path = "vi-VN.lproj/Localizable.strings"; sourceTree = ""; }; - C396469F2509D41100B0B9F5 /* id-ID */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "id-ID"; path = "id-ID.lproj/Localizable.strings"; sourceTree = ""; }; C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKReachabilityManager.swift; sourceTree = ""; }; C3A71D0A2558989C0043A11F /* MessageWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageWrapper.swift; sourceTree = ""; }; C3A71D1C25589AC30043A11F /* WebSocketProto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocketProto.swift; sourceTree = ""; }; C3A71D1D25589AC30043A11F /* WebSocketResources.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocketResources.pb.swift; sourceTree = ""; }; C3A71F882558BA9F0043A11F /* Mnemonic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mnemonic.swift; sourceTree = ""; }; - C3A8AF752665B03900A467FE /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = ""; }; - C3A8AF762665F97A00A467FE /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; C3AAFFF125AE99710089E6DD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C3ADC66026426688005F1414 /* ShareNavController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareNavController.swift; sourceTree = ""; }; - C3AECBEA24EF5244005743DE /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/Localizable.strings; sourceTree = ""; }; C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FixedWidthInteger+BigEndian.swift"; sourceTree = ""; }; C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionSnodeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C3C2A5A1255385C100C340D1 /* SessionSnodeKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionSnodeKit.h; sourceTree = ""; }; @@ -1598,7 +1575,6 @@ C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditClosedGroupVC.swift; sourceTree = ""; }; C3ECBF7A257056B700EA7FCE /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = ""; }; - C3F0A5B2255C915C007BE2A3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; D221A089169C9E5E00537ABF /* Session.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Session.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1853,6 +1829,50 @@ FD8ECF912938552800C0D1BB /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; FD8ECF93293856AF00C0D1BB /* Randomness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Randomness.swift; sourceTree = ""; }; FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; + FD9401A42ABD04AC003A4834 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + FD9401A52ABD04AC003A4834 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; + FD9401A62ABD04AC003A4834 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; }; + FD9401A72ABD04AC003A4834 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + FD9401A82ABD04AC003A4834 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/Localizable.strings; sourceTree = ""; }; + FD9401A92ABD04AC003A4834 /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/Localizable.strings"; sourceTree = ""; }; + FD9401AA2ABD04AC003A4834 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + FD9401AB2ABD04AC003A4834 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; + FD9401AC2ABD04AC003A4834 /* ku */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ku; path = ku.lproj/Localizable.strings; sourceTree = ""; }; + FD9401AD2ABD04AC003A4834 /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Localizable.strings; sourceTree = ""; }; + FD9401AE2ABD04AC003A4834 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + FD9401AF2ABD04AC003A4834 /* eo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eo; path = eo.lproj/Localizable.strings; sourceTree = ""; }; + FD9401B02ABD04AC003A4834 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + FD9401B12ABD04AC003A4834 /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; + FD9401B22ABD04AC003A4834 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; + FD9401B32ABD04AC003A4834 /* es-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-ES"; path = "es-ES.lproj/Localizable.strings"; sourceTree = ""; }; + FD9401B42ABD04AC003A4834 /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/Localizable.strings; sourceTree = ""; }; + FD9401B52ABD04AC003A4834 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; + FD9401B62ABD04AC003A4834 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; + FD9401B72ABD04AC003A4834 /* fil */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fil; path = fil.lproj/Localizable.strings; sourceTree = ""; }; + FD9401B82ABD04AC003A4834 /* ne-NP */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ne-NP"; path = "ne-NP.lproj/Localizable.strings"; sourceTree = ""; }; + FD9401B92ABD04AC003A4834 /* no */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = no; path = no.lproj/Localizable.strings; sourceTree = ""; }; + FD9401BA2ABD04AC003A4834 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; + FD9401BB2ABD04AC003A4834 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; + FD9401BC2ABD04AC003A4834 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; + FD9401BD2ABD04AC003A4834 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + FD9401BE2ABD04AC003A4834 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + FD9401BF2ABD04AC003A4834 /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lv; path = lv.lproj/Localizable.strings; sourceTree = ""; }; + FD9401C02ABD04AC003A4834 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = ""; }; + FD9401C12ABD04AC003A4834 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + FD9401C22ABD04AC003A4834 /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = ""; }; + FD9401C32ABD04AC003A4834 /* si-LK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "si-LK"; path = "si-LK.lproj/Localizable.strings"; sourceTree = ""; }; + FD9401C42ABD04AC003A4834 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + FD9401C52ABD04AC003A4834 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + FD9401C62ABD04AC003A4834 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; }; + FD9401C72ABD04AC003A4834 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + FD9401C82ABD04AC003A4834 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = ""; }; + FD9401C92ABD04AC003A4834 /* sv-SE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sv-SE"; path = "sv-SE.lproj/Localizable.strings"; sourceTree = ""; }; + FD9401CA2ABD04AC003A4834 /* bn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bn; path = bn.lproj/Localizable.strings; sourceTree = ""; }; + FD9401CB2ABD04AC003A4834 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; + FD9401CC2ABD04AC003A4834 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + FD9401CE2ABD04AC003A4834 /* TRANSLATIONS.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = TRANSLATIONS.md; sourceTree = ""; }; + FD9401CF2ABD04AC003A4834 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; + FD9401D02ABD04AC003A4834 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = ""; }; FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSendJobSpec.swift; sourceTree = ""; }; FD96F3A629DBD43D00401309 /* MockJobRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockJobRunner.swift; sourceTree = ""; }; FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARC4RandomNumberGenerator.swift; sourceTree = ""; }; @@ -2426,15 +2446,6 @@ path = TurnServers; sourceTree = ""; }; - B6B6C3C419193F5B00C0B76B /* Translations */ = { - isa = PBXGroup; - children = ( - B6F509951AA53F760068F56A /* Localizable.strings */, - ); - name = Translations; - path = ..; - sourceTree = ""; - }; B8041A7325C8F758003C2166 /* Content Views */ = { isa = PBXGroup; children = ( @@ -3430,8 +3441,8 @@ D221A095169C9E5E00537ABF /* Session-Info.plist */, 4C63CBFF210A620B003AE45C /* SignalTSan.supp */, 4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */, + FD9401A22ABD04AC003A4834 /* Translations */, 34074F54203D0722004596AE /* Sounds */, - B6B6C3C419193F5B00C0B76B /* Translations */, ); path = Meta; sourceTree = ""; @@ -4123,6 +4134,15 @@ path = JobRunner; sourceTree = ""; }; + FD9401A22ABD04AC003A4834 /* Translations */ = { + isa = PBXGroup; + children = ( + FD9401CE2ABD04AC003A4834 /* TRANSLATIONS.md */, + FD9401A32ABD04AC003A4834 /* Localizable.strings */, + ); + path = Translations; + sourceTree = ""; + }; FD96F3A229DBC3BA00401309 /* Jobs */ = { isa = PBXGroup; children = ( @@ -4897,6 +4917,36 @@ sv, th, si, + ar, + uk, + be, + el, + "zh-TW", + ku, + sl, + da, + eo, + bg, + "es-ES", + cs, + ko, + fil, + "ne-NP", + no, + nb, + hu, + tr, + "pt-BR", + vi, + lv, + lt, + "zh-CN", + "si-LK", + id, + "sv-SE", + bn, + "pt-PT", + ro, ); mainGroup = D221A07E169C9E5E00537ABF; productRefGroup = D221A08A169C9E5E00537ABF /* Products */; @@ -4925,7 +4975,6 @@ buildActionMask = 2147483647; files = ( 4535186E1FC635DD00210559 /* MainInterface.storyboard in Resources */, - 347850551FD749C0007B8332 /* Localizable.strings in Resources */, B8D07406265C683A00F77E07 /* ElegantIcons.ttf in Resources */, 3478504C1FD7496D007B8332 /* Images.xcassets in Resources */, ); @@ -4935,7 +4984,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4982,7 +5030,6 @@ 4C6F527C20FFE8400097DEEE /* SignalUBSan.supp in Resources */, 34CF078A203E6B78005C4D61 /* end_call_tone_cept.caf in Resources */, C3CA3AA2255CDADA00F4C6D4 /* english.txt in Resources */, - B6F509971AA53F760068F56A /* Localizable.strings in Resources */, B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */, 34CF0788203E6B78005C4D61 /* ringback_tone_ansi.caf in Resources */, 7BFD1A972747689000FB91B9 /* Session-Turn-Server in Resources */, @@ -4995,6 +5042,7 @@ B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */, 34CF0787203E6B78005C4D61 /* busy_tone_ansi.caf in Resources */, 45A2F005204473A3002E978A /* NewMessage.aifc in Resources */, + FD9401D12ABD04AC003A4834 /* Localizable.strings in Resources */, 45B74A882044AAB600CD42F8 /* aurora.aifc in Resources */, 45B74A742044AAB600CD42F8 /* aurora-quiet.aifc in Resources */, 7B0EFDF4275490EA00FFAAE7 /* ringing.mp3 in Resources */, @@ -6395,34 +6443,54 @@ name = MainInterface.storyboard; sourceTree = ""; }; - B6F509951AA53F760068F56A /* Localizable.strings */ = { + FD9401A32ABD04AC003A4834 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - B676BCEF1AA544E7009637B8 /* de */, - B676BCF11AA5451E009637B8 /* es */, - B646D10E1AA5461A004133BA /* fr */, - B68CB7DC1AA547100065AC3F /* pt_BR */, - B68CB7E01AA548420065AC3F /* ru */, - B68CB7E61AA548870065AC3F /* zh_CN */, - 4C1D2337218B6BA000A0598F /* it */, - C3AECBEA24EF5244005743DE /* fa */, - C396469C2509D3ED00B0B9F5 /* pl */, - C396469D2509D3F400B0B9F5 /* ja */, - C396469E2509D40400B0B9F5 /* vi-VN */, - C396469F2509D41100B0B9F5 /* id-ID */, - C3F0A5B2255C915C007BE2A3 /* en */, - B8EB20E6263F7E4B00773E52 /* sk */, - B87588582644CA9D000E60D0 /* nl */, - B834C6DD26533AE5001091B2 /* zh-Hant */, - C3A8AF752665B03900A467FE /* hi */, - C3A8AF762665F97A00A467FE /* fi */, - B8BAC75B2695645400EA1759 /* hr */, - B8BAC75C2695648500EA1759 /* sv */, - B8BAC75D2695649000EA1759 /* th */, - 7B2DB2AD26F1B0FF0035B509 /* si */, + FD9401A42ABD04AC003A4834 /* de */, + FD9401A52ABD04AC003A4834 /* ar */, + FD9401A62ABD04AC003A4834 /* el */, + FD9401A72ABD04AC003A4834 /* ja */, + FD9401A82ABD04AC003A4834 /* fa */, + FD9401A92ABD04AC003A4834 /* zh-TW */, + FD9401AA2ABD04AC003A4834 /* en */, + FD9401AB2ABD04AC003A4834 /* uk */, + FD9401AC2ABD04AC003A4834 /* ku */, + FD9401AD2ABD04AC003A4834 /* sl */, + FD9401AE2ABD04AC003A4834 /* da */, + FD9401AF2ABD04AC003A4834 /* eo */, + FD9401B02ABD04AC003A4834 /* it */, + FD9401B12ABD04AC003A4834 /* bg */, + FD9401B22ABD04AC003A4834 /* sk */, + FD9401B32ABD04AC003A4834 /* es-ES */, + FD9401B42ABD04AC003A4834 /* be */, + FD9401B52ABD04AC003A4834 /* cs */, + FD9401B62ABD04AC003A4834 /* ko */, + FD9401B72ABD04AC003A4834 /* fil */, + FD9401B82ABD04AC003A4834 /* ne-NP */, + FD9401B92ABD04AC003A4834 /* no */, + FD9401BA2ABD04AC003A4834 /* hu */, + FD9401BB2ABD04AC003A4834 /* tr */, + FD9401BC2ABD04AC003A4834 /* pl */, + FD9401BD2ABD04AC003A4834 /* pt-BR */, + FD9401BE2ABD04AC003A4834 /* vi */, + FD9401BF2ABD04AC003A4834 /* lv */, + FD9401C02ABD04AC003A4834 /* lt */, + FD9401C12ABD04AC003A4834 /* ru */, + FD9401C22ABD04AC003A4834 /* zh-CN */, + FD9401C32ABD04AC003A4834 /* si-LK */, + FD9401C42ABD04AC003A4834 /* fr */, + FD9401C52ABD04AC003A4834 /* fi */, + FD9401C62ABD04AC003A4834 /* id */, + FD9401C72ABD04AC003A4834 /* nl */, + FD9401C82ABD04AC003A4834 /* th */, + FD9401C92ABD04AC003A4834 /* sv-SE */, + FD9401CA2ABD04AC003A4834 /* bn */, + FD9401CB2ABD04AC003A4834 /* pt-PT */, + FD9401CC2ABD04AC003A4834 /* ro */, + FD9401CF2ABD04AC003A4834 /* hr */, + FD9401D02ABD04AC003A4834 /* hi */, ); name = Localizable.strings; - path = Meta/Translations; sourceTree = ""; }; /* End PBXVariantGroup section */ diff --git a/Session/Conversations/Input View/ExpandingAttachmentsButton.swift b/Session/Conversations/Input View/ExpandingAttachmentsButton.swift index 85757803b..d77ede7a0 100644 --- a/Session/Conversations/Input View/ExpandingAttachmentsButton.swift +++ b/Session/Conversations/Input View/ExpandingAttachmentsButton.swift @@ -34,6 +34,7 @@ final class ExpandingAttachmentsButton: UIView, InputViewButtonDelegate { lazy var documentButton: InputViewButton = { let result = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_document_black"), delegate: self, hasOpaqueBackground: true) result.accessibilityIdentifier = "Documents folder" + result.accessibilityLabel = "accessibility_document_button".localized() result.isAccessibilityElement = true return result @@ -42,6 +43,7 @@ final class ExpandingAttachmentsButton: UIView, InputViewButtonDelegate { lazy var libraryButton: InputViewButton = { let result = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_camera_roll_black"), delegate: self, hasOpaqueBackground: true) result.accessibilityIdentifier = "Images folder" + result.accessibilityLabel = "accessibility_library_button".localized() result.isAccessibilityElement = true return result @@ -50,6 +52,7 @@ final class ExpandingAttachmentsButton: UIView, InputViewButtonDelegate { lazy var cameraButton: InputViewButton = { let result = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_camera_black"), delegate: self, hasOpaqueBackground: true) result.accessibilityIdentifier = "Select camera button" + result.accessibilityLabel = "accessibility_camera_button".localized() result.isAccessibilityElement = true return result diff --git a/Session/Meta/Translations/ar.lproj/Localizable.strings b/Session/Meta/Translations/ar.lproj/Localizable.strings new file mode 100644 index 000000000..cb4148379 --- /dev/null +++ b/Session/Meta/Translations/ar.lproj/Localizable.strings @@ -0,0 +1,782 @@ +/* No comment provided by engineer. */ +"ATTACHMENT" = "ملف مرفق"; +/* Title for 'caption' mode of the attachment approval view. */ +"ATTACHMENT_APPROVAL_CAPTION_TITLE" = "تعليق"; +/* Format string for file extension label in call interstitial view */ +"ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT" = "نوع الملف: %@"; +/* Format string for file size label in call interstitial view. Embeds: {{file size as 'N mb' or 'N kb'}}. */ +"ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT" = "الحجم: %@"; +/* One-line label indicating the user can add no more text to the media message field. */ +"ATTACHMENT_APPROVAL_MESSAGE_LENGTH_LIMIT_REACHED" = "تم بلوغ حد الرسالة"; +/* Label for 'send' button in the 'attachment approval' dialog. */ +"ATTACHMENT_APPROVAL_SEND_BUTTON" = "أرسل"; +/* Generic filename for an attachment with no known name */ +"ATTACHMENT_DEFAULT_FILENAME" = "ملف مرفق"; +/* The title of the 'attachment error' alert. */ +"ATTACHMENT_ERROR_ALERT_TITLE" = "خطأ في إرسال المرفق"; +/* Attachment error message for image attachments which could not be converted to JPEG */ +"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "غير قادر على تحويل الصوره."; +/* Attachment error message for video attachments which could not be converted to MP4 */ +"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "غير قادر على معالجة الفديو."; +/* Attachment error message for image attachments which cannot be parsed */ +"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "غير قار على تحليل الصورة."; +/* Attachment error message for image attachments in which metadata could not be removed */ +"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "غير قادر على حذف البيانات الوصفية من الصوره."; +/* Attachment error message for image attachments which could not be resized */ +"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "غير قادر على تعديل الصورة."; +/* Attachment error message for attachments whose data exceed file size limits */ +"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "حجم المرفق عالي."; +/* Attachment error message for attachments with invalid data */ +"ATTACHMENT_ERROR_INVALID_DATA" = "المرفق يحتوي على بيانات غير صحيحة."; +/* Attachment error message for attachments with an invalid file format */ +"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "صيغة المرفق غير صالحة."; +/* Attachment error message for attachments without any data */ +"ATTACHMENT_ERROR_MISSING_DATA" = "المرفق فارغ."; +/* Alert title when picking a document fails for an unknown reason */ +"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "لقد فشل اختيار الوثيقة."; +/* Alert body when picking a document fails because user picked a directory/bundle */ +"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_BODY" = "الرجاء إنشاء أرشيف مضغوط لهذا الملف أو المجلد ومحاولة إرسال ذلك بدلاً من ذلك."; +/* Alert title when picking a document fails because user picked a directory/bundle */ +"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_TITLE" = "ملف غير مدعوم"; +/* Short text label for a voice message attachment, used for thread preview and on the lock screen */ +"ATTACHMENT_TYPE_VOICE_MESSAGE" = "رسالة صوتية"; +/* Button label for the 'block' button */ +"BLOCK_LIST_BLOCK_BUTTON" = "حظر"; +/* A format for the 'block user' action sheet title. Embeds {{the blocked user's name or phone number}}. */ +"BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "منع٪؜@؟"; +/* A format for the 'unblock user' action sheet title. Embeds {{the unblocked user's name or phone number}}. */ +"BLOCK_LIST_UNBLOCK_TITLE_FORMAT" = "فتح المنع٪؜@؟"; +/* Button label for the 'unblock' button */ +"BLOCK_LIST_UNBLOCK_BUTTON" = "إلغاء الحظر"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ تم حظره"; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "تم حظر المستخدم"; +/* Alert title after unblocking a group or 1:1 chat. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_UNBLOCKED_ALERT_TITLE_FORMAT" = "تم حظر المستخدم %@."; +/* An explanation of the consequences of blocking another user. */ +"BLOCK_USER_BEHAVIOR_EXPLANATION" = "لن يتمكن المستخدمين المحظورين من الاتصال بك أو إرسال رسائل إليك."; +/* Label for generic done button. */ +"BUTTON_DONE" = "تم"; +/* Button text to enable batch selection mode */ +"BUTTON_SELECT" = "حدد"; +/* keyboard toolbar label when starting to search with no current results */ +"CONVERSATION_SEARCH_SEARCHING" = "جاري البحث..."; +/* keyboard toolbar label when no messages match the search string */ +"CONVERSATION_SEARCH_NO_RESULTS" = "لا يوجد تطابق"; +/* keyboard toolbar label when exactly 1 message matches the search string */ +"CONVERSATION_SEARCH_ONE_RESULT" = "مطابقة واحدة"; +/* keyboard toolbar label when more than 1 message matches the search string. Embeds {{number/position of the 'currently viewed' result}} and the {{total number of results}} */ +"CONVERSATION_SEARCH_RESULTS_FORMAT" = "%d من %d مطابقة"; +/* table cell label in conversation settings */ +"CONVERSATION_SETTINGS_BLOCK_THIS_USER" = "حظر هذا المستخدم"; +/* label for 'mute thread' cell in conversation settings */ +"CONVERSATION_SETTINGS_MUTE_LABEL" = "اكتم المحادثة"; +/* Table cell label in conversation settings which returns the user to the conversation with 'search mode' activated */ +"CONVERSATION_SETTINGS_SEARCH" = "بحث عن محادثة"; +/* Title for the 'crop/scale image' dialog. */ +"CROP_SCALE_IMAGE_VIEW_TITLE" = "تحريك وتعديل"; +/* Subtitle shown while the app is updating its database. */ +"DATABASE_VIEW_OVERLAY_SUBTITLE" = "قد يستغرق ذلك بضع دقائق."; +/* Title shown while the app is updating its database. */ +"DATABASE_VIEW_OVERLAY_TITLE" = "تحسين قاعدة البيانات"; +/* The present; the current time. */ +"DATE_NOW" = "الآن"; +/* table cell label in conversation settings */ +"DISAPPEARING_MESSAGES" = "الرسائل المؤقتة"; +/* table cell label in conversation settings */ +"EDIT_GROUP_ACTION" = "تعديل المجموعة"; +/* Label indicating media gallery is empty */ +"GALLERY_TILES_EMPTY_GALLERY" = "ليس لديك أي وسائط في هذه المحادثة."; +/* Label indicating loading is in progress */ +"GALLERY_TILES_LOADING_MORE_RECENT_LABEL" = "جارٍ تحميل الوسائط الأحدث…"; +/* Label indicating loading is in progress */ +"GALLERY_TILES_LOADING_OLDER_LABEL" = "جارٍ تحميل الوسائط الأقدم…"; +/* Error displayed when there is a failure fetching a GIF from the remote service. */ +"GIF_PICKER_ERROR_FETCH_FAILURE" = "فشل في جلب GIF. الرجاء التحقق من أنك متصل بالإنترنت."; +/* Generic error displayed when picking a GIF */ +"GIF_PICKER_ERROR_GENERIC" = "حدث خطأ غير معروف."; +/* Shown when selected GIF couldn't be fetched */ +"GIF_PICKER_FAILURE_ALERT_TITLE" = "تعذر اختيار GIF"; +/* Alert message shown when user tries to search for GIFs without entering any search terms. */ +"GIF_PICKER_VIEW_MISSING_QUERY" = "الرجاء إدخال الكلمة الأساسية للبحث."; +/* Indicates that an error occurred while searching. */ +"GIF_VIEW_SEARCH_ERROR" = "خطأ. اضغط لإعادة المحاولة."; +/* Indicates that the user's search had no results. */ +"GIF_VIEW_SEARCH_NO_RESULTS" = "لا توجد نتائج."; +/* No comment provided by engineer. */ +"GROUP_CREATED" = "تم إنشاء المجموعة"; +/* No comment provided by engineer. */ +"GROUP_MEMBER_JOINED" = "%@ انضم إلى المجموعة. "; +/* No comment provided by engineer. */ +"GROUP_MEMBER_LEFT" = "%@ غادر المجموعة. "; +/* No comment provided by engineer. */ +"GROUP_MEMBER_REMOVED" = "تمت إزالة %@ من المجموعة. "; +/* No comment provided by engineer. */ +"GROUP_MEMBERS_REMOVED" = "تمت إزالة %@ من المجموعة. "; +/* No comment provided by engineer. */ +"GROUP_TITLE_CHANGED" = "تم تغيير العنوان إلى '%@'. "; +/* No comment provided by engineer. */ +"GROUP_UPDATED" = "تم تحديث المجموعة."; +/* No comment provided by engineer. */ +"GROUP_YOU_LEFT" = "لقد غادرت المجموعة."; +/* No comment provided by engineer. */ +"YOU_WERE_REMOVED" = " تمت إزالتك من المجموعة. "; +/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */ +"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "لا يمكنك مشاركة اكثر من %@ عنصر."; +/* alert title */ +"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "فشل في تحديد المرفق."; +/* Message for the alert indicating that an audio file is invalid. */ +"INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE" = "الملف الصوتي غير صالح."; +/* Confirmation button within contextual alert */ +"LEAVE_BUTTON_TITLE" = "مغادرة"; +/* table cell label in conversation settings */ +"LEAVE_GROUP_ACTION" = "مغادرة المجموعة"; +/* nav bar button item */ +"MEDIA_DETAIL_VIEW_ALL_MEDIA_BUTTON" = "جميع الوسائط"; +/* Confirmation button text to delete selected media from the gallery, embeds {{number of messages}} */ +"MEDIA_GALLERY_DELETE_MULTIPLE_MESSAGES_FORMAT" = "حذف %d رسالة"; +/* Confirmation button text to delete selected media message from the gallery */ +"MEDIA_GALLERY_DELETE_SINGLE_MESSAGE" = "حذف الرسالة"; +/* embeds {{sender name}} and {{sent datetime}}, e.g. 'Sarah on 10/30/18, 3:29' */ +"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ في %@"; +/* Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}. */ +"MEDIA_GALLERY_MORE_ITEMS_FORMAT" = "+%@"; +/* Short sender label for media sent by you */ +"MEDIA_GALLERY_SENDER_NAME_YOU" = "أنت"; +/* Section header in media gallery collection view */ +"MEDIA_GALLERY_THIS_MONTH_HEADER" = "هذا الشهر"; +/* status message for failed messages */ +"MESSAGE_STATUS_FAILED" = "فشل الإرسال."; +/* status message for read messages */ +"MESSAGE_STATUS_READ" = "مقروءة"; +/* message status while message is sending. */ +"MESSAGE_STATUS_SENDING" = "جاري الإرسال…"; +/* status message for sent messages */ +"MESSAGE_STATUS_SENT" = "مُرسل"; +/* status message while attachment is uploading */ +"MESSAGE_STATUS_UPLOADING" = "يُرسل ملف…"; +/* notification title. Embeds {{author name}} and {{group name}} */ +"NEW_GROUP_MESSAGE_NOTIFICATION_TITLE" = "%@ إلى %@"; +/* Label for 1:1 conversation with yourself. */ +"NOTE_TO_SELF" = "ملاحظة شخصية"; +/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */ +"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "ربما تلقيت رسائل أثناء إعادة تشغيل %@ الخاص بك."; +/* No comment provided by engineer. */ +"BUTTON_OK" = "حسناً"; +/* Info Message when {{other user}} disables or doesn't support disappearing messages */ +"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "عطّل %@ خاصية إختفاء الرسائل التلقائي."; +/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */ +"OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ حدد وقت اختفاء الرسالة إلى %@"; +/* alert title, generic error preventing user from capturing a photo */ +"PHOTO_CAPTURE_GENERIC_ERROR" = "غير قادر على إلتقاط الصورة."; +/* alert title */ +"PHOTO_CAPTURE_UNABLE_TO_CAPTURE_IMAGE" = "غير قادر على إلتقاط الصورة."; +/* alert title */ +"PHOTO_CAPTURE_UNABLE_TO_INITIALIZE_CAMERA" = "فشل في تهيئة الكاميرا."; +/* label for system photo collections which have no name. */ +"PHOTO_PICKER_UNNAMED_COLLECTION" = "ألبوم بدون إسم"; +/* Notification action button title */ +"PUSH_MANAGER_MARKREAD" = "اعتبارها مقروءة"; +/* Notification action button title */ +"PUSH_MANAGER_REPLY" = "رد"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "يرجى المصادقة لفتح التطبيق."; +/* Title for alert indicating that screen lock could not be unlocked. */ +"SCREEN_LOCK_UNLOCK_FAILED" = "فشل التحقق"; +/* alert title when user attempts to leave the send media flow when they have an in-progress album */ +"SEND_MEDIA_ABANDON_TITLE" = "حذف الوسائط؟"; +/* alert action, confirming the user wants to exit the media flow and abandon any photos they've taken */ +"SEND_MEDIA_CONFIRM_ABANDON_ALBUM" = "تم تجاهل الوسائط"; +/* Format string for the default 'Note' sound. Embeds the system {{sound name}}. */ +"SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT" = "%@(الافتراضي)"; +/* Label for settings view that allows user to change the notification sound. */ +"SETTINGS_ITEM_NOTIFICATION_SOUND" = "صوت الرسالة"; +/* Label for the 'no sound' option that allows users to disable sounds for notifications, etc. */ +"SOUNDS_NONE" = "لا شيء "; +/* {{number of days}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 days}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_DAYS" = "%@ أيام"; +/* Label text below navbar button, embeds {{number of days}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5d' not '5 d'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_DAYS_SHORT_FORMAT" = "%@يوم"; +/* {{number of hours}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 hours}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_HOURS" = "%@ ساعات"; +/* Label text below navbar button, embeds {{number of hours}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5h' not '5 h'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_HOURS_SHORT_FORMAT" = "%@ساعة"; +/* {{number of minutes}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 minutes}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_MINUTES" = "%@ دقائق"; +/* Label text below navbar button, embeds {{number of minutes}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5m' not '5 m'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_MINUTES_SHORT_FORMAT" = "%@دقيقة"; +/* {{number of seconds}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 seconds}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SECONDS" = "%@ ثواني"; +/* Label text below navbar button, embeds {{number of seconds}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5s' not '5 s'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SECONDS_SHORT_FORMAT" = "%@ثانية"; +/* {{1 day}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 day}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_DAY" = "%@ يوم"; +/* {{1 hour}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 hour}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_HOUR" = "%@ ساعة"; +/* {{1 minute}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 minute}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_MINUTE" = "%@ دقيقة"; +/* {{1 week}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 week}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_WEEK" = "%@ أسبوع"; +/* {{number of weeks}}, embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 weeks}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_WEEKS" = "%@ أسابيع"; +/* Label text below navbar button, embeds {{number of weeks}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5w' not '5 w'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_WEEKS_SHORT_FORMAT" = "%@اسبوع"; +/* Label for the cancel button in an alert or action sheet. */ +"TXT_CANCEL_TITLE" = "إلغاء"; +/* No comment provided by engineer. */ +"TXT_DELETE_TITLE" = "حذف"; +/* Filename for voice messages. */ +"VOICE_MESSAGE_FILE_NAME" = "رسالة صوتية"; +/* Message for the alert indicating the 'voice message' needs to be held to be held down to record. */ +"VOICE_MESSAGE_TOO_SHORT_ALERT_MESSAGE" = "اضغط باستمرار لتسجيل رسالة صوتية."; +/* Title for the alert indicating the 'voice message' needs to be held to be held down to record. */ +"VOICE_MESSAGE_TOO_SHORT_ALERT_TITLE" = "رسالة صوتية"; +/* Info Message when you disable disappearing messages */ +"YOU_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "لقد قمت بتعطيل خاصية الرسائل المؤقتة."; +/* Info message embedding a {{time amount}}, see the *_TIME_AMOUNT strings for context. */ +"YOU_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "قمت بتعيين وقت اختفاء الرسالة إلى %@"; +// MARK: - Session +"continue_2" = "التالي"; +"copy" = "نسخ"; +"invalid_url" = "عنوان URL غير صحيح"; +"next" = "التالي"; +"share" = "مشاركة"; +"invalid_session_id" = "معرف سيشن غير صحيح"; +"cancel" = "إلغاء"; +"your_session_id" = "معرف سيشن الخاص بك"; +"vc_landing_title_2" = "تبدأ جلستك هنا..."; +"vc_landing_register_button_title" = "إنشاء معرف سيشن"; +"vc_landing_restore_button_title" = "استئناف الجَلسة"; +"vc_landing_link_button_title" = "ربط جهاز"; +"view_fake_chat_bubble_1" = "ما (Session)؟"; +"view_fake_chat_bubble_2" = "هو تطبيق مراسلة مشفر, لامركزي"; +"view_fake_chat_bubble_3" = "إذن لا يجمع معلوماتي الشخصية ولا المعلومات حول محادثاتي؟ كيف يعمل؟"; +"view_fake_chat_bubble_4" = "باِستخدام مدمج من تقنيات التشفير من الطرفين و توجيه مجهول المسار للمعلومات."; +"view_fake_chat_bubble_5" = "الأصدقاء لا يتركون أصدقائهم يستعملون تطبيق مراسلة مكشوف. مرحبا بك."; +"vc_register_title" = "رحب بعنوان تعريفك"; +"vc_register_explanation" = "عنوان تعريفك هو عنوان وحيد خاص بك, يمكن للناس الاتصال بك عن طريقه. دون معرفة هويتك الحقيقية, هذا العنوان مصمم ليكون خاص و مجهول."; +"vc_restore_title" = "استرجع حسابك"; +"vc_restore_explanation" = "أدخل عبارة الاسترجاع التي أُعطيت لك عندما سجلت الدخول لاِسترجاع حسابك."; +"vc_restore_seed_text_field_hint" = "أدخل عبارة الاسترجاع"; +"vc_link_device_title" = "ربط جهاز"; +"vc_link_device_scan_qr_code_tab_title" = "امسح رمز الاستجابة السريع"; +"vc_display_name_title_2" = "اختر اسم العرض الخاص بك"; +"vc_display_name_explanation" = "هذا سيكون اِسمك عندما تستخدم Session. قد يكون اِسمك الحقيقي, اِسم مستعار أو اي شئ آخر تريده."; +"vc_display_name_text_field_hint" = "أدخل اسم العرض"; +"vc_display_name_display_name_missing_error" = "الرجاء اختيار اسم العرض"; +"vc_display_name_display_name_too_long_error" = "الرجاء اختيار اسم عرض أقصر"; +"vc_pn_mode_recommended_option_tag" = "مستحسن"; +"vc_pn_mode_no_option_picked_modal_title" = "اِختر"; +"vc_home_empty_state_message" = "ليس لديك أي جهة اتصال بعد"; +"vc_home_empty_state_button_title" = "ابدأ Session"; +"vc_seed_title" = "عبارة الاسترجاع الخاصة بك"; +"vc_seed_title_2" = "هذه هي عبارة الاسترجاع الخاصة بك"; +"vc_seed_explanation" = "عبارة الاسترجاع هي مفتاح لحسابك - يمكنك استخدامها لاسترجاع حسابك إذا فقدت الوصول لجهازك. احفظها في مكان آمن و لا تعطها أي أحد."; +"vc_seed_reveal_button_title" = "انقر مطولاً للكشف"; +"view_seed_reminder_subtitle_1" = "أمّن حسابك بحفظ عبارة استرجاع الحساب"; +"view_seed_reminder_subtitle_2" = "اضغط مع الاستمرار على الكلمات المخفاة للكشف عن عبارة الاسترداد الخاصة بم، ثم خزنها بأمان لحماية حسابك."; +"view_seed_reminder_subtitle_3" = "تأكد من الإحتفاظ بكلمات الإسترجاع الخاصة بك في مكان آمن"; +"vc_path_title" = "مسار"; +"vc_path_explanation" = "يقوم session بإخفاء عنوانIP الخاص بك بتمرير الرسائل عبر عدة خوادم في شبكة Session اللامركزية. هذه هي الدول التي يمر عبرها اتصالك:"; +"vc_path_device_row_title" = "أنت"; +"vc_path_guard_node_row_title" = "نقطة الدخول"; +"vc_path_service_node_row_title" = "نقطة الخدمة"; +"vc_path_destination_row_title" = "الوجهة"; +"vc_path_learn_more_button_title" = "معرفة المزيد"; +"vc_create_private_chat_title" = "رسالة جديدة"; +"vc_create_private_chat_enter_session_id_tab_title" = "أدخِل عنوان التعريف"; +"vc_create_private_chat_scan_qr_code_tab_title" = "امسح رمز الاستجابة السريع"; +"vc_enter_public_key_explanation" = "ابدأ محادثة جديدة بإدخال معرف جلسة شخص ما أو مشاركة معرفك معهم."; +"vc_scan_qr_code_camera_access_explanation" = "تطبيق\"الجلسة\"يحتاج الوصول إلى الكاميرا لمسح رموز الاستجابة السريعة"; +"vc_scan_qr_code_grant_camera_access_button_title" = "منح صلاحية الكاميرا"; +"vc_create_closed_group_title" = "انشئ مجموعة"; +"vc_create_closed_group_text_field_hint" = "أدخل إسم المجموعة"; +"vc_create_closed_group_empty_state_message" = "ليست لديك أية جهات اتصال حتى الآن"; +"vc_create_closed_group_group_name_missing_error" = "الرجاء إدخال إسم للمجموعة"; +"vc_create_closed_group_group_name_too_long_error" = "الرجاء إدخال إسم مجموعة أقصر"; +"vc_create_closed_group_too_many_group_members_error" = "المجموعة المغلقة لا يمكن ان تحتوي على أكثر من ١٠٠ مستخدم"; +"vc_join_public_chat_title" = "انضم إلى مجتمع"; +"vc_join_public_chat_enter_group_url_tab_title" = "رابط المجتمع"; +"vc_join_public_chat_scan_qr_code_tab_title" = "مسح رمز QR"; +"vc_enter_chat_url_text_field_hint" = "أدخل رابط المجتمع"; +"vc_settings_title" = "الإعدادات"; +"vc_group_settings_title" = "إعدادات المجموعة"; +"vc_settings_display_name_missing_error" = "الرجاء إختيار إسم العرض"; +"vc_settings_display_name_too_long_error" = "الرجاء إختيار اسم عرض أقصر"; +"vc_settings_privacy_button_title" = "الخصوصية"; +"vc_settings_notifications_button_title" = "الإشعارات"; +"vc_settings_recovery_phrase_button_title" = "عبارة الاسترجاع"; +"vc_settings_clear_all_data_button_title" = "مسح البيانات"; +"vc_qr_code_title" = "رمز الاستجابة السريع"; +"vc_qr_code_view_my_qr_code_tab_title" = "اظهر لي\"رمز الاستجابة السريع\" الخاص بي"; +"vc_qr_code_view_scan_qr_code_tab_title" = "مسح رمز QR"; +"vc_qr_code_view_scan_qr_code_explanation" = "امسح رمز QR لشخص ما لبدء محادثة معهم"; +"vc_view_my_qr_code_explanation" = "هذا هو رمز QR الخاص بك. يمكن للمستخدمين الآخرين فحصه لبدء جلسة معك."; +// MARK: - Not Yet Translated +"fast_mode_explanation" = "You’ll be notified of new messages reliably and immediately using Apple’s notification servers."; +"fast_mode" = "الوضع السريع"; +"slow_mode_explanation" = "Session will occasionally check for new messages in the background."; +"slow_mode" = "الوضع البطيئ"; +"vc_pn_mode_title" = "Message Notifications"; +"vc_link_device_recovery_phrase_tab_title" = "Recovery Phrase"; +"vc_link_device_scan_qr_code_explanation" = "Navigate to Settings → Recovery Phrase on your other device to show your QR code."; +"vc_enter_recovery_phrase_title" = "Recovery Phrase"; +"vc_enter_recovery_phrase_explanation" = "To link your device, enter the recovery phrase that was given to you when you signed up."; +"vc_enter_public_key_text_field_hint" = "Enter Session ID or ONS name"; +"admin_group_leave_warning" = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone."; +"vc_join_open_group_suggestions_title" = "Or join one of these..."; +"vc_settings_invite_a_friend_button_title" = "Invite a Friend"; +"copied" = "Copied"; +"vc_conversation_settings_copy_session_id_button_title" = "Copy Session ID"; +"vc_conversation_input_prompt" = "Message"; +"vc_conversation_voice_message_cancel_message" = "Slide to Cancel"; +"modal_download_attachment_title" = "Trust %@?"; +"modal_download_attachment_explanation" = "Are you sure you want to download media sent by %@?"; +"modal_download_button_title" = "Download"; +"modal_open_url_title" = "Open URL?"; +"modal_open_url_explanation" = "Are you sure you want to open %@?"; +"modal_open_url_button_title" = "Open"; +"modal_copy_url_button_title" = "Copy Link"; +"modal_blocked_title" = "Unblock %@?"; +"modal_blocked_explanation" = "Are you sure you want to unblock %@?"; +"modal_blocked_button_title" = "Unblock"; +"modal_link_previews_title" = "Enable Link Previews?"; +"modal_link_previews_explanation" = "Enabling link previews will show previews for URLs you send and receive. This can be useful, but Session will need to contact linked websites to generate previews. You can always disable link previews in Session's settings."; +"modal_link_previews_button_title" = "Enable"; +"vc_share_title" = "Share to Session"; +"vc_share_loading_message" = "Preparing attachments..."; +"vc_share_sending_message" = "Sending..."; +"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link"; +"vc_share_link_previews_error" = "Unable to load preview"; +"vc_share_link_previews_disabled_title" = "Link Previews Disabled"; +"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings."; +"view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "إضافة أعضاء"; +"modal_send_seed_title" = "تحذير"; +"modal_send_seed_explanation" = "This is your recovery phrase. If you send it to someone they'll have full access to your account."; +"modal_send_seed_send_button_title" = "إرسل"; +"vc_conversation_settings_notify_for_mentions_only_title" = "إشعار للإشارات فقط"; +"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you."; +"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only"; +"message_deleted" = "This message has been deleted"; +"delete_message_for_me" = "حذف لي فقط"; +"delete_message_for_everyone" = "حذف للجميع"; +"delete_message_for_me_and_recipient" = "Delete for me and %@"; +"context_menu_reply" = "رد"; +"context_menu_save" = "حفظ"; +"context_menu_ban_user" = "حظر المستخدم"; +"context_menu_ban_and_delete_all" = "حظر وحذف الكل"; +"context_menu_ban_user_error_alert_message" = "Unable to ban user"; +"accessibility_expanding_attachments_button" = "أضافه مرفقات"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "مستند"; +"accessibility_library_button" = "مكتبة الصور"; +"accessibility_camera_button" = "الكاميرا"; +"accessibility_main_button_collapse" = "إغلاق خيارات المرفق"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; +"DISMISS_BUTTON_TEXT" = "تجاهل"; +/* Button text which opens the settings app */ +"OPEN_SETTINGS_BUTTON" = "الإعدادات"; +"call_outgoing" = "You called %@"; +"call_incoming" = "%@ called you"; +"call_missed" = "مكالمة فائتة من %@"; +"APN_Message" = "لديك رسالة جديدة."; +"APN_Collapsed_Messages" = "لديك %@ رسائل جديدة."; +"PIN_BUTTON_TEXT" = "Pin"; +"UNPIN_BUTTON_TEXT" = "Unpin"; +"modal_call_missed_tips_title" = "Call missed"; +"modal_call_missed_tips_explanation" = "Call missed from '%@' because you needed to enable the 'Voice and video calls' permission in the Privacy Settings."; +"media_saved" = "Media saved by %@."; +"screenshot_taken" = "%@ took a screenshot."; +"SEARCH_SECTION_CONTACTS" = "Contacts & Groups"; +"SEARCH_SECTION_MESSAGES" = "Messages"; +"MESSAGE_REQUESTS_TITLE" = "Message Requests"; +"MESSAGE_REQUESTS_EMPTY_TEXT" = "No pending message requests"; +"MESSAGE_REQUESTS_CLEAR_ALL" = "Clear All"; +"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; +"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; +"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_BLOCK_CONFIRMATION_ACTON" = "Are you sure you want to block this contact?"; +"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request and reveal your Session ID."; +"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; +"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; +"TXT_HIDE_TITLE" = "Hide"; +"TXT_DELETE_ACCEPT" = "Accept"; +"TXT_BLOCK_USER_TITLE" = "Block User"; +"ALERT_ERROR_TITLE" = "Error"; +"modal_call_permission_request_title" = "Call Permissions Required"; +"modal_call_permission_request_explanation" = "You can enable the 'Voice and video calls' permission in the Privacy Settings."; +"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; +"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; +"LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; +"RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; +"RECOVERY_PHASE_ERROR_LAST_WORD" = "You seem to be missing the last word of your recovery phrase. Please check what you entered and try again."; +"RECOVERY_PHASE_ERROR_INVALID_WORD" = "There appears to be an invalid word in your recovery phrase. Please check what you entered and try again."; +"RECOVERY_PHASE_ERROR_FAILED" = "Your recovery phrase couldn't be verified. Please check what you entered and try again."; +/* Indicates that an unknown error occurred while using Touch ID/Face ID/Phone Passcode. */ +"SCREEN_LOCK_ENABLE_UNKNOWN_ERROR" = "Authentication could not be accessed."; +/* Indicates that Touch ID/Face ID/Phone Passcode authentication failed. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_FAILED" = "Authentication failed."; +/* Indicates that Touch ID/Face ID/Phone Passcode is 'locked out' on this device due to authentication failures. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_LOCKOUT" = "Too many failed authentication attempts. Please try again later."; +/* Indicates that Touch ID/Face ID/Phone Passcode are not available on this device. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE" = "You must enable a passcode in your iOS Settings in order to use Screen Lock."; +/* Indicates that Touch ID/Face ID/Phone Passcode is not configured on this device. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_ENROLLED" = "You must enable a passcode in your iOS Settings in order to use Screen Lock."; +/* Indicates that Touch ID/Face ID/Phone Passcode passcode is not set. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_PASSCODE_NOT_SET" = "You must enable a passcode in your iOS Settings in order to use Screen Lock."; +/* Label for the button to send a message */ +"SEND_BUTTON_TITLE" = "Send"; +/* Generic text for button that retries whatever the last action was. */ +"RETRY_BUTTON_TEXT" = "Retry"; +/* notification action */ +"SHOW_THREAD_BUTTON_TITLE" = "Show Chat"; +/* notification body */ +"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; +"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; +"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"MEDIA_TAB_TITLE" = "Media"; +"DOCUMENT_TAB_TITLE" = "Documents"; +"DOCUMENT_TILES_EMPTY_DOCUMENT" = "You don't have any document in this conversation."; +"DOCUMENT_TILES_LOADING_MORE_RECENT_LABEL" = "Loading Newer Document…"; +"DOCUMENT_TILES_LOADING_OLDER_LABEL" = "Loading Older Document…"; +/* The name for the emoji category 'Activities' */ +"EMOJI_CATEGORY_ACTIVITIES_NAME" = "Activities"; +/* The name for the emoji category 'Animals & Nature' */ +"EMOJI_CATEGORY_ANIMALS_NAME" = "Animals & Nature"; +/* The name for the emoji category 'Flags' */ +"EMOJI_CATEGORY_FLAGS_NAME" = "Flags"; +/* The name for the emoji category 'Food & Drink' */ +"EMOJI_CATEGORY_FOOD_NAME" = "Food & Drink"; +/* The name for the emoji category 'Objects' */ +"EMOJI_CATEGORY_OBJECTS_NAME" = "Objects"; +/* The name for the emoji category 'Recents' */ +"EMOJI_CATEGORY_RECENTS_NAME" = "Recently Used"; +/* The name for the emoji category 'Smileys & People' */ +"EMOJI_CATEGORY_SMILEYSANDPEOPLE_NAME" = "Smileys & People"; +/* The name for the emoji category 'Symbols' */ +"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols"; +/* The name for the emoji category 'Travel & Places' */ +"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places"; +"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@."; +"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message."; +"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message."; +"EMOJI_REACTS_RATE_LIMIT_TOAST" = "Slow down! You've sent too many emoji reacts. Try again soon."; +/* New conversation screen*/ +"vc_new_conversation_title" = "New Conversation"; +"CREATE_GROUP_BUTTON_TITLE" = "Create"; +"JOIN_COMMUNITY_BUTTON_TITLE" = "Join"; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one conversations."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; +"NOTIFICATIONS_TITLE" = "Notifications"; +"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; +"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; +"NOTIFICATIONS_STRATEGY_FAST_MODE_DESCRIPTION" = "You'll be notified of new message reliably and immediately using Apple's notification servers."; +"NOTIFICATIONS_STRATEGY_FAST_MODE_ACTION" = "Go to device notification settings"; +"NOTIFICATIONS_SECTION_STYLE" = "Notification Style"; +"NOTIFICATIONS_STYLE_SOUND_TITLE" = "Sound"; +"NOTIFICATIONS_STYLE_SOUND_WHEN_OPEN_TITLE" = "Sound When App is Open"; +"NOTIFICATIONS_STYLE_CONTENT_TITLE" = "Notification Content"; +"NOTIFICATIONS_STYLE_CONTENT_DESCRIPTION" = "The information shown in notifications."; +"NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT" = "Name & Content"; +"NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_ONLY" = "Name Only"; +"NOTIFICATIONS_STYLE_CONTENT_OPTION_NO_NAME_OR_CONTENT" = "No Name or Content"; +"CONVERSATION_SETTINGS_TITLE" = "Conversations"; +"CONVERSATION_SETTINGS_SECTION_MESSAGE_TRIMMING" = "Message Trimming"; +"CONVERSATION_SETTINGS_MESSAGE_TRIMMING_TITLE" = "Trim Communities"; +"CONVERSATION_SETTINGS_MESSAGE_TRIMMING_DESCRIPTION" = "Delete messages older than 6 months from Communities that have over 2,000 messages."; +"CONVERSATION_SETTINGS_SECTION_AUDIO_MESSAGES" = "Audio Messages"; +"CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_TITLE" = "Autoplay Audio Messages"; +"CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_DESCRIPTION" = "Autoplay consecutive audio messages."; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_TITLE" = "Blocked Contacts"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_EMPTY_STATE" = "You have no blocked contacts."; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK" = "Unblock"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_SINGLE" = "Are you sure you want to unblock %@?"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_FALLBACK" = "this contact"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_1" = "Are you sure you want to unblock %@"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_2_SINGLE" = "and %@?"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_3" = "and %d others?"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_ACTON" = "Unblock"; +"APPEARANCE_TITLE" = "Appearance"; +"APPEARANCE_THEMES_TITLE" = "Themes"; +"APPEARANCE_PRIMARY_COLOR_TITLE" = "Primary colour"; +"APPEARANCE_PRIMARY_COLOR_PREVIEW_INC_QUOTE" = "How are you?"; +"APPEARANCE_PRIMARY_COLOR_PREVIEW_INC_MESSAGE" = "I'm good thanks, you?"; +"APPEARANCE_PRIMARY_COLOR_PREVIEW_OUT_MESSAGE" = "I'm doing great, thanks."; +"APPEARANCE_NIGHT_MODE_TITLE" = "Auto night-mode"; +"APPEARANCE_NIGHT_MODE_TOGGLE" = "Match system settings"; +"HELP_TITLE" = "Help"; +"HELP_REPORT_BUG_TITLE" = "Report a Bug"; +"HELP_REPORT_BUG_DESCRIPTION" = "Export your logs, then upload the file though Session's Help Desk."; +"HELP_REPORT_BUG_ACTION_TITLE" = "Export Logs"; +"HELP_TRANSLATE_TITLE" = "Translate Session"; +"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; +"HELP_FAQ_TITLE" = "FAQ"; +"HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Clear All Data"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; +"modal_clear_all_data_confirm" = "Clear"; +"modal_seed_title" = "Your Recovery Phrase"; +"modal_seed_explanation" = "You can use your recovery phrase to restore your account or link a device."; +"modal_permission_explanation" = "Session needs %@ access to continue. You can enable access in the iOS settings."; +"modal_permission_settings_title" = "Settings"; +"modal_permission_camera" = "camera"; +"modal_permission_microphone" = "microphone"; +"modal_permission_library" = "library"; +"DISAPPEARING_MESSAGES_OFF" = "Off"; +"DISAPPEARING_MESSAGES_SUBTITLE_OFF" = "Off"; +"DISAPPEARING_MESSAGES_SUBTITLE_DISAPPEAR_AFTER" = "Disappear After: %@"; +"COPY_GROUP_URL" = "Copy Group URL"; +"NEW_CONVERSATION_CONTACTS_SECTION_TITLE" = "Contacts"; +"GROUP_ERROR_NO_MEMBER_SELECTION" = "Please pick at least 1 group member"; +"GROUP_CREATION_PLEASE_WAIT" = "Please wait while the group is created..."; +"GROUP_CREATION_ERROR_TITLE" = "Couldn't Create Group"; +"GROUP_CREATION_ERROR_MESSAGE" = "Please check your internet connection and try again."; +"GROUP_UPDATE_ERROR_TITLE" = "Couldn't Update Group"; +"GROUP_UPDATE_ERROR_MESSAGE" = "Can't leave while adding or removing other members."; +"GROUP_ACTION_REMOVE" = "Remove"; +"GROUP_TITLE_MEMBERS" = "Members"; +"GROUP_TITLE_FALLBACK" = "Group"; +"DM_ERROR_DIRECT_BLINDED_ID" = "You can only send messages to Blinded IDs from within a Community"; +"DM_ERROR_INVALID" = "Please check the Session ID or ONS name and try again"; +"COMMUNITY_ERROR_INVALID_URL" = "Please check the URL you entered and try again."; +"COMMUNITY_ERROR_GENERIC" = "Couldn't Join"; +"DISAPPERING_MESSAGES_TITLE" = "Disappearing Messages"; +"DISAPPERING_MESSAGES_TYPE_TITLE" = "Delete Type"; +"DISAPPERING_MESSAGES_TYPE_AFTER_READ_TITLE" = "Disappear After Read"; +"DISAPPERING_MESSAGES_TYPE_AFTER_READ_DESCRIPTION" = "Messages delete after they have been read."; +"DISAPPERING_MESSAGES_TYPE_AFTER_SEND_TITLE" = "Disappear After Send"; +"DISAPPERING_MESSAGES_TYPE_AFTER_SEND_DESCRIPTION" = "Messages delete after they have been sent."; +"DISAPPERING_MESSAGES_TIMER_TITLE" = "Timer"; +"DISAPPERING_MESSAGES_SAVE_TITLE" = "Set"; +"DISAPPERING_MESSAGES_GROUP_WARNING" = "This setting applies to everyone in this conversation."; +"DISAPPERING_MESSAGES_GROUP_WARNING_ADMIN_ONLY" = "This setting applies to everyone in this conversation. Only group admins can change this setting."; +"DISAPPERING_MESSAGES_SUMMARY" = "Disappear After %@ - %@"; +"DISAPPERING_MESSAGES_INFO_ENABLE" = "%@ has set messages to disappear %@ after they have been %@"; +"DISAPPERING_MESSAGES_INFO_UPDATE" = "%@ has changed messages to disappear %@ after they have been %@"; +"DISAPPERING_MESSAGES_INFO_DISABLE" = "%@ has turned off disappearing messages"; + +/* context_menu_info */ +"context_menu_info" = "Info"; + +/* An error that is displayed when the application fails for create it's initial connection to the database */ +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; + +/* A warning displayed to the user when the application takes too long to launch */ +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; + +/* The title of a button on a modal shown when the application fails to start, pressing the button closes the application */ +"APP_STARTUP_EXIT" = "Exit"; + +/* An error which occurs if the user tries to restore the database after an initial failure and it fails to restore */ +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; + +/* Text displayed in place of a quoted message when the original message is not on the device */ +"QUOTED_MESSAGE_NOT_FOUND" = "Original message not found."; + +/* EMOJI_REACTS_SHOW_LESS */ +"EMOJI_REACTS_SHOW_LESS" = "Show less"; + +/* PRIVACY_SECTION_MESSAGE_REQUESTS */ +"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests"; + +/* PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE */ +"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests"; + +/* PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION */ +"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations."; + +/* Information displayed above the input when sending a message to a new user for the first time explaining limitations around the types of messages which can be sent before being approved */ +"MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; + +/* State of a message while it's still in the process of being sent */ +"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending"; + +/* State of a message once it has been sent */ +"MESSAGE_DELIVERY_STATUS_SENT" = "Sent"; + +/* State of a message after the recipient has read the message */ +"MESSAGE_DELIVERY_STATUS_READ" = "Read"; + +/* State of a message if it failed to be sent */ +"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; + +/* Title of the message information screen describing the date/time a message was sent */ +"MESSAGE_INFO_SENT" = "Sent"; + +/* Title of the message information screen describing the date/time a message was received on a specific device */ +"MESSAGE_INFO_RECEIVED" = "Received"; + +/* Title of the message information screen describing the sender of the message */ +"MESSAGE_INFO_FROM" = "From"; + +/* Title of the message information screen describing the identifier of the attachment */ +"ATTACHMENT_INFO_FILE_ID" = "File ID"; + +/* Title of the message information screen describing the file type of the attachment */ +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; + +/* Title of the message information screen describing the size of the attachment */ +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; + +/* Title on the message information screen describing the resolution of a media attachment */ +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; + +/* Title on the message information screen describing the duration of a media attachment */ +"ATTACHMENT_INFO_DURATION" = "Duration"; + +/* State of a message after it failed to sync to the current users other devices */ +"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync"; + +/* State of a message while it's in the process of being synced to the users other devices */ +"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing"; + +/* Title of the modal that appears after a user taps on the state of a message which failed to send */ +"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message"; + +/* Title of the modal that appears after a user taps on the state of a message which failed to sync to the users other devices */ +"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices"; + +/* Action for the modal shown when asking the user whether they want to delete from all of their devices */ +"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; + +/* Action in the long-press menu to trigger a message to be sent again after it has failed */ +"context_menu_resend" = "Resend"; + +/* Action in the long-press menu to trigger a message to be synced again after it has failed */ +"context_menu_resync" = "Resync"; + +/* Title of a modal show the first time a user tries to search for GIFs */ +"GIPHY_PERMISSION_TITLE" = "Search GIFs?"; + +/* Message of a modal show the first time a user tries to search for GIFs */ +"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; + +/* Action in the long-press menu to view more information about a specific message */ +"message_info_title" = "Message Info"; + +/* Action to mute a conversation in the swipe menu */ +"mute_button_text" = "Mute"; + +/* Action in the swipe menu to unmute a conversation */ +"unmute_button_text" = "Unmute"; + +/* Action in the swipe menu to mark a conversation as read */ +"MARK_AS_READ" = "Mark read"; + +/* Action in the swipe menu to mark a conversation as unread */ +"MARK_AS_UNREAD" = "Mark unread"; + +/* Title of the confirmation modal show when attempting to leave a group conversation */ +"leave_group_confirmation_alert_title" = "Leave Group"; + +/* Title of the confirmation modal show when attempting to leave a community conversation */ +"leave_community_confirmation_alert_title" = "Leave Community"; + +/* Message in the confirmation modal when leaving a community conversation */ +"leave_community_confirmation_alert_message" = "Are you sure you want to leave %@?"; + +/* Conversation subtitle while the user in the process of leaving */ +"group_you_leaving" = "Leaving..."; + +/* Conversation subtitle if the user in the failed to leave */ +"group_leave_error" = "Failed to leave Group!"; + +/* Message within a conversation indicating the device was unable to leave a group conversation */ +"group_unable_to_leave" = "Unable to leave the Group, please try again"; + +/* Title in the confirmation modal to delete a group */ +"delete_group_confirmation_alert_title" = "Delete Group"; + +/* Message in the confirmation modal to delete a group */ +"delete_group_confirmation_alert_message" = "Are you sure you want to delete %@?"; + +/* Title in the confirmation modal when the user tries to delete a one-to-one conversation */ +"delete_conversation_confirmation_alert_title" = "Delete Conversation"; + +/* Message in the confirmation modal when the user tries to delete a one-to-one conversation */ +"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; + +/* Title in the confirmation modal when the user tries to hide the 'Note to Self' conversation */ +"hide_note_to_self_confirmation_alert_title" = "Hide Note to Self"; + +/* Message in the confirmation modal when the user tries to hide the 'Note to Self' conversation */ +"hide_note_to_self_confirmation_alert_message" = "Are you sure you want to hide %@?"; + +/* Title in the modal for updating the users profile display picture */ +"update_profile_modal_title" = "Set Display Picture"; + +/* Save action in the modal for updating the users profile display picture */ +"update_profile_modal_save" = "Save"; + +/* Remove action in the modal for updating the users profile display picture */ +"update_profile_modal_remove" = "Remove"; + +/* Title for the error when failing to remove the users profile display picture */ +"update_profile_modal_remove_error_title" = "Unable to remove avatar image"; + +/* Title for the error when the user selects a profile display picture that is too large */ +"update_profile_modal_max_size_error_title" = "Maximum File Size Exceeded"; + +/* Message for the error when the user selects a profile display picture that is too large */ +"update_profile_modal_max_size_error_message" = "Please select a smaller photo and try again"; + +/* Title for the error when the user fails to update their profile display picture */ +"update_profile_modal_error_title" = "Couldn't Update Profile"; + +/* Message for the error when the user fails to update their profile display picture */ +"update_profile_modal_error_message" = "Please check your internet connection and try again"; + +/* Placeholder when entering a nickname for a contact */ +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; + +/* The separator within a conversation indicating that following messages are unread */ +"UNREAD_MESSAGES" = "Unread Messages"; + +/* Empty state for a conversation */ +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; + +/* Empty state for a read-only conversation */ +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; + +/* Empty state for the 'Note to Self' conversation */ +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; + +/* Message to indicate a user has Community Message Requests disabled */ +"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message."; + +/* Warning to indicate one of the users devices is running an old version of Session */ +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; + +/* Ann error displayed if the device is unable to retrieve the users recovery password */ +"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue."; + +/* An error displayed when trying to send a message if the device is unable to save it to the database */ +"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages."; + +/* An error indicating that the device was unable to access the database for some reason */ +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/be.lproj/Localizable.strings b/Session/Meta/Translations/be.lproj/Localizable.strings new file mode 100644 index 000000000..f377c4606 --- /dev/null +++ b/Session/Meta/Translations/be.lproj/Localizable.strings @@ -0,0 +1,782 @@ +/* No comment provided by engineer. */ +"ATTACHMENT" = "Прыкладанне"; +/* Title for 'caption' mode of the attachment approval view. */ +"ATTACHMENT_APPROVAL_CAPTION_TITLE" = "Загаловак"; +/* Format string for file extension label in call interstitial view */ +"ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT" = "Тып файла:% type%"; +/* Format string for file size label in call interstitial view. Embeds: {{file size as 'N mb' or 'N kb'}}. */ +"ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT" = "Памер: %@"; +/* One-line label indicating the user can add no more text to the media message field. */ +"ATTACHMENT_APPROVAL_MESSAGE_LENGTH_LIMIT_REACHED" = "Ліміт паведамленняў дасягнуты"; +/* Label for 'send' button in the 'attachment approval' dialog. */ +"ATTACHMENT_APPROVAL_SEND_BUTTON" = "Адправіць"; +/* Generic filename for an attachment with no known name */ +"ATTACHMENT_DEFAULT_FILENAME" = "Прыкладанне"; +/* The title of the 'attachment error' alert. */ +"ATTACHMENT_ERROR_ALERT_TITLE" = "Памылка адпраўкі ўкладання"; +/* Attachment error message for image attachments which could not be converted to JPEG */ +"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Немагчыма канвертаваць малюнак."; +/* Attachment error message for video attachments which could not be converted to MP4 */ +"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Немагчыма апрацаваць відэа."; +/* Attachment error message for image attachments which cannot be parsed */ +"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Немагчыма прааналізаваць малюнак."; +/* Attachment error message for image attachments in which metadata could not be removed */ +"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Немагчыма выдаліць метададзеныя з малюнку."; +/* Attachment error message for image attachments which could not be resized */ +"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Немагчыма змяніць памер малюнка."; +/* Attachment error message for attachments whose data exceed file size limits */ +"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Укладанне занадта вялікае."; +/* Attachment error message for attachments with invalid data */ +"ATTACHMENT_ERROR_INVALID_DATA" = "Далучэнне змяшчае несапраўдны кантэнт."; +/* Attachment error message for attachments with an invalid file format */ +"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Далучэнне мае няправільны фармат файла."; +/* Attachment error message for attachments without any data */ +"ATTACHMENT_ERROR_MISSING_DATA" = "Attachment is empty."; +/* Alert title when picking a document fails for an unknown reason */ +"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Failed to choose document."; +/* Alert body when picking a document fails because user picked a directory/bundle */ +"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_BODY" = "Please create a compressed archive of this file or directory and try sending that instead."; +/* Alert title when picking a document fails because user picked a directory/bundle */ +"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_TITLE" = "Unsupported File"; +/* Short text label for a voice message attachment, used for thread preview and on the lock screen */ +"ATTACHMENT_TYPE_VOICE_MESSAGE" = "Voice Message"; +/* Button label for the 'block' button */ +"BLOCK_LIST_BLOCK_BUTTON" = "Блакаваць"; +/* A format for the 'block user' action sheet title. Embeds {{the blocked user's name or phone number}}. */ +"BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Block %@?"; +/* A format for the 'unblock user' action sheet title. Embeds {{the unblocked user's name or phone number}}. */ +"BLOCK_LIST_UNBLOCK_TITLE_FORMAT" = "Unblock %@?"; +/* Button label for the 'unblock' button */ +"BLOCK_LIST_UNBLOCK_BUTTON" = "Разблакіраваць"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@ has been blocked."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Карыстальнік заблакіраваны"; +/* Alert title after unblocking a group or 1:1 chat. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_UNBLOCKED_ALERT_TITLE_FORMAT" = "%@ has been unblocked."; +/* An explanation of the consequences of blocking another user. */ +"BLOCK_USER_BEHAVIOR_EXPLANATION" = "Заблакіраваныя карыстальнікі не змогуць тэлефанаваць вам або адпраўляць вам паведамленні."; +/* Label for generic done button. */ +"BUTTON_DONE" = "Зроблена"; +/* Button text to enable batch selection mode */ +"BUTTON_SELECT" = "Выбраць"; +/* keyboard toolbar label when starting to search with no current results */ +"CONVERSATION_SEARCH_SEARCHING" = "Searching..."; +/* keyboard toolbar label when no messages match the search string */ +"CONVERSATION_SEARCH_NO_RESULTS" = "No matches"; +/* keyboard toolbar label when exactly 1 message matches the search string */ +"CONVERSATION_SEARCH_ONE_RESULT" = "1 матч"; +/* keyboard toolbar label when more than 1 message matches the search string. Embeds {{number/position of the 'currently viewed' result}} and the {{total number of results}} */ +"CONVERSATION_SEARCH_RESULTS_FORMAT" = "%d of %d matches"; +/* table cell label in conversation settings */ +"CONVERSATION_SETTINGS_BLOCK_THIS_USER" = "Заблакіраваць гэтага карыстальніка"; +/* label for 'mute thread' cell in conversation settings */ +"CONVERSATION_SETTINGS_MUTE_LABEL" = "Без гуку"; +/* Table cell label in conversation settings which returns the user to the conversation with 'search mode' activated */ +"CONVERSATION_SETTINGS_SEARCH" = "Search Conversation"; +/* Title for the 'crop/scale image' dialog. */ +"CROP_SCALE_IMAGE_VIEW_TITLE" = "Move and Scale"; +/* Subtitle shown while the app is updating its database. */ +"DATABASE_VIEW_OVERLAY_SUBTITLE" = "This can take a few minutes."; +/* Title shown while the app is updating its database. */ +"DATABASE_VIEW_OVERLAY_TITLE" = "Optimizing Database"; +/* The present; the current time. */ +"DATE_NOW" = "Цяпер"; +/* table cell label in conversation settings */ +"DISAPPEARING_MESSAGES" = "Знікаючыя паведамленні"; +/* table cell label in conversation settings */ +"EDIT_GROUP_ACTION" = "Рэдагаваць групу"; +/* Label indicating media gallery is empty */ +"GALLERY_TILES_EMPTY_GALLERY" = "Вы не маеце ніякай інфармацыі ў гэтай тэме."; +/* Label indicating loading is in progress */ +"GALLERY_TILES_LOADING_MORE_RECENT_LABEL" = "Загрузка новых медыя…"; +/* Label indicating loading is in progress */ +"GALLERY_TILES_LOADING_OLDER_LABEL" = "Загрузка старых медыя…"; +/* Error displayed when there is a failure fetching a GIF from the remote service. */ +"GIF_PICKER_ERROR_FETCH_FAILURE" = "Не атрымалася атрымаць запытаны GIF. Упэўніцеся, што вы знаходзіцеся ў Інтэрнэце."; +/* Generic error displayed when picking a GIF */ +"GIF_PICKER_ERROR_GENERIC" = "Адбылася невядомая памылка."; +/* Shown when selected GIF couldn't be fetched */ +"GIF_PICKER_FAILURE_ALERT_TITLE" = "Немагчыма выбраць GIF"; +/* Alert message shown when user tries to search for GIFs without entering any search terms. */ +"GIF_PICKER_VIEW_MISSING_QUERY" = "Калі ласка, увядзіце пошук."; +/* Indicates that an error occurred while searching. */ +"GIF_VIEW_SEARCH_ERROR" = "Памылка. Націсніце, каб паўтарыць спробу."; +/* Indicates that the user's search had no results. */ +"GIF_VIEW_SEARCH_NO_RESULTS" = "Нічога не знойдзена."; +/* No comment provided by engineer. */ +"GROUP_CREATED" = "Група створана"; +/* No comment provided by engineer. */ +"GROUP_MEMBER_JOINED" = "%@ joined the group. "; +/* No comment provided by engineer. */ +"GROUP_MEMBER_LEFT" = "%@ left the group. "; +/* No comment provided by engineer. */ +"GROUP_MEMBER_REMOVED" = "%@ was removed from the group. "; +/* No comment provided by engineer. */ +"GROUP_MEMBERS_REMOVED" = "%@ were removed from the group. "; +/* No comment provided by engineer. */ +"GROUP_TITLE_CHANGED" = "Title is now '%@'. "; +/* No comment provided by engineer. */ +"GROUP_UPDATED" = "Group updated."; +/* No comment provided by engineer. */ +"GROUP_YOU_LEFT" = "You have left the group."; +/* No comment provided by engineer. */ +"YOU_WERE_REMOVED" = " You were removed from the group. "; +/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */ +"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items."; +/* alert title */ +"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Failed to select attachment."; +/* Message for the alert indicating that an audio file is invalid. */ +"INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE" = "Invalid audio file."; +/* Confirmation button within contextual alert */ +"LEAVE_BUTTON_TITLE" = "Пакінуць"; +/* table cell label in conversation settings */ +"LEAVE_GROUP_ACTION" = "Пакінуць групу"; +/* nav bar button item */ +"MEDIA_DETAIL_VIEW_ALL_MEDIA_BUTTON" = "Усе медыя"; +/* Confirmation button text to delete selected media from the gallery, embeds {{number of messages}} */ +"MEDIA_GALLERY_DELETE_MULTIPLE_MESSAGES_FORMAT" = "Выдаліць %d паведамленняў"; +/* Confirmation button text to delete selected media message from the gallery */ +"MEDIA_GALLERY_DELETE_SINGLE_MESSAGE" = "Выдаліць паведамленне"; +/* embeds {{sender name}} and {{sent datetime}}, e.g. 'Sarah on 10/30/18, 3:29' */ +"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ на %@"; +/* Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}. */ +"MEDIA_GALLERY_MORE_ITEMS_FORMAT" = "+%@"; +/* Short sender label for media sent by you */ +"MEDIA_GALLERY_SENDER_NAME_YOU" = "You"; +/* Section header in media gallery collection view */ +"MEDIA_GALLERY_THIS_MONTH_HEADER" = "This Month"; +/* status message for failed messages */ +"MESSAGE_STATUS_FAILED" = "Sending failed."; +/* status message for read messages */ +"MESSAGE_STATUS_READ" = "Read"; +/* message status while message is sending. */ +"MESSAGE_STATUS_SENDING" = "Адпраўка…"; +/* status message for sent messages */ +"MESSAGE_STATUS_SENT" = "Адпраўлена"; +/* status message while attachment is uploading */ +"MESSAGE_STATUS_UPLOADING" = "Загрузка…"; +/* notification title. Embeds {{author name}} and {{group name}} */ +"NEW_GROUP_MESSAGE_NOTIFICATION_TITLE" = "%@ да %@"; +/* Label for 1:1 conversation with yourself. */ +"NOTE_TO_SELF" = "Нататка для сябе"; +/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */ +"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "Заданне можа быць адноўлена з паведамленнямі пасля перазапуску."; +/* No comment provided by engineer. */ +"BUTTON_OK" = "Добра"; +/* Info Message when {{other user}} disables or doesn't support disappearing messages */ +"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ disabled disappearing messages."; +/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */ +"OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ set disappearing message time to %@"; +/* alert title, generic error preventing user from capturing a photo */ +"PHOTO_CAPTURE_GENERIC_ERROR" = "Unable to capture image."; +/* alert title */ +"PHOTO_CAPTURE_UNABLE_TO_CAPTURE_IMAGE" = "Unable to capture image."; +/* alert title */ +"PHOTO_CAPTURE_UNABLE_TO_INITIALIZE_CAMERA" = "Failed to configure camera."; +/* label for system photo collections which have no name. */ +"PHOTO_PICKER_UNNAMED_COLLECTION" = "Unnamed Album"; +/* Notification action button title */ +"PUSH_MANAGER_MARKREAD" = "Mark as Read"; +/* Notification action button title */ +"PUSH_MANAGER_REPLY" = "Reply"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Authenticate to open Session."; +/* Title for alert indicating that screen lock could not be unlocked. */ +"SCREEN_LOCK_UNLOCK_FAILED" = "Authentication Failed"; +/* alert title when user attempts to leave the send media flow when they have an in-progress album */ +"SEND_MEDIA_ABANDON_TITLE" = "Discard Media?"; +/* alert action, confirming the user wants to exit the media flow and abandon any photos they've taken */ +"SEND_MEDIA_CONFIRM_ABANDON_ALBUM" = "Discard Media"; +/* Format string for the default 'Note' sound. Embeds the system {{sound name}}. */ +"SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT" = "%@ (default)"; +/* Label for settings view that allows user to change the notification sound. */ +"SETTINGS_ITEM_NOTIFICATION_SOUND" = "Message Sound"; +/* Label for the 'no sound' option that allows users to disable sounds for notifications, etc. */ +"SOUNDS_NONE" = "None"; +/* {{number of days}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 days}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_DAYS" = "%@ days"; +/* Label text below navbar button, embeds {{number of days}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5d' not '5 d'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_DAYS_SHORT_FORMAT" = "%@d"; +/* {{number of hours}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 hours}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_HOURS" = "%@ hours"; +/* Label text below navbar button, embeds {{number of hours}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5h' not '5 h'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_HOURS_SHORT_FORMAT" = "%@h"; +/* {{number of minutes}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 minutes}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_MINUTES" = "%@ minutes"; +/* Label text below navbar button, embeds {{number of minutes}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5m' not '5 m'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_MINUTES_SHORT_FORMAT" = "%@m"; +/* {{number of seconds}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 seconds}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SECONDS" = "%@ seconds"; +/* Label text below navbar button, embeds {{number of seconds}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5s' not '5 s'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SECONDS_SHORT_FORMAT" = "%@s"; +/* {{1 day}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 day}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_DAY" = "%@ day"; +/* {{1 hour}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 hour}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_HOUR" = "%@ hour"; +/* {{1 minute}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 minute}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_MINUTE" = "%@ minute"; +/* {{1 week}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 week}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_WEEK" = "%@ week"; +/* {{number of weeks}}, embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 weeks}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_WEEKS" = "%@ weeks"; +/* Label text below navbar button, embeds {{number of weeks}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5w' not '5 w'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_WEEKS_SHORT_FORMAT" = "%@w"; +/* Label for the cancel button in an alert or action sheet. */ +"TXT_CANCEL_TITLE" = "Cancel"; +/* No comment provided by engineer. */ +"TXT_DELETE_TITLE" = "Delete"; +/* Filename for voice messages. */ +"VOICE_MESSAGE_FILE_NAME" = "Voice Message"; +/* Message for the alert indicating the 'voice message' needs to be held to be held down to record. */ +"VOICE_MESSAGE_TOO_SHORT_ALERT_MESSAGE" = "Tap and hold to record a voice message."; +/* Title for the alert indicating the 'voice message' needs to be held to be held down to record. */ +"VOICE_MESSAGE_TOO_SHORT_ALERT_TITLE" = "Voice Message"; +/* Info Message when you disable disappearing messages */ +"YOU_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "You disabled disappearing messages."; +/* Info message embedding a {{time amount}}, see the *_TIME_AMOUNT strings for context. */ +"YOU_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "You set disappearing message time to %@"; +// MARK: - Session +"continue_2" = "Continue"; +"copy" = "Copy"; +"invalid_url" = "Invalid URL"; +"next" = "Next"; +"share" = "Share"; +"invalid_session_id" = "Invalid Session ID"; +"cancel" = "Cancel"; +"your_session_id" = "Your Session ID"; +"vc_landing_title_2" = "Your Session begins here..."; +"vc_landing_register_button_title" = "Create Session ID"; +"vc_landing_restore_button_title" = "Continue Your Session"; +"vc_landing_link_button_title" = "Link a Device"; +"view_fake_chat_bubble_1" = "What's Session?"; +"view_fake_chat_bubble_2" = "It's a decentralized, encrypted messaging app"; +"view_fake_chat_bubble_3" = "So it doesn't collect my personal information or my conversation metadata? How does it work?"; +"view_fake_chat_bubble_4" = "Using a combination of advanced anonymous routing and end-to-end encryption technologies."; +"view_fake_chat_bubble_5" = "Friends don't let friends use compromised messengers. You're welcome."; +"vc_register_title" = "Say hello to your Session ID"; +"vc_register_explanation" = "Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design."; +"vc_restore_title" = "Restore your account"; +"vc_restore_explanation" = "Enter the recovery phrase that was given to you when you signed up to restore your account."; +"vc_restore_seed_text_field_hint" = "Enter your recovery phrase"; +"vc_link_device_title" = "Link a Device"; +"vc_link_device_scan_qr_code_tab_title" = "Scan QR Code"; +"vc_display_name_title_2" = "Pick your display name"; +"vc_display_name_explanation" = "This will be your name when you use Session. It can be your real name, an alias, or anything else you like."; +"vc_display_name_text_field_hint" = "Enter a display name"; +"vc_display_name_display_name_missing_error" = "Please pick a display name"; +"vc_display_name_display_name_too_long_error" = "Please pick a shorter display name"; +"vc_pn_mode_recommended_option_tag" = "Recommended"; +"vc_pn_mode_no_option_picked_modal_title" = "Please Pick an Option"; +"vc_home_empty_state_message" = "You don't have any contacts yet"; +"vc_home_empty_state_button_title" = "Start a Session"; +"vc_seed_title" = "Your Recovery Phrase"; +"vc_seed_title_2" = "Meet your recovery phrase"; +"vc_seed_explanation" = "Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don’t give it to anyone."; +"vc_seed_reveal_button_title" = "Hold to reveal"; +"view_seed_reminder_subtitle_1" = "Secure your account by saving your recovery phrase"; +"view_seed_reminder_subtitle_2" = "Tap and hold the redacted words to reveal your recovery phrase, then store it safely to secure your Session ID."; +"view_seed_reminder_subtitle_3" = "Make sure to store your recovery phrase in a safe place"; +"vc_path_title" = "Path"; +"vc_path_explanation" = "Session hides your IP by routing your messages through multiple Service Nodes in Session's decentralized network. These are the countries your connection is currently being routed through:"; +"vc_path_device_row_title" = "You"; +"vc_path_guard_node_row_title" = "Entry Node"; +"vc_path_service_node_row_title" = "Service Node"; +"vc_path_destination_row_title" = "Destination"; +"vc_path_learn_more_button_title" = "Learn More"; +"vc_create_private_chat_title" = "New Message"; +"vc_create_private_chat_enter_session_id_tab_title" = "Enter Session ID"; +"vc_create_private_chat_scan_qr_code_tab_title" = "Scan QR Code"; +"vc_enter_public_key_explanation" = "Start a new conversation by entering someone's Session ID or share your Session ID with them."; +"vc_scan_qr_code_camera_access_explanation" = "Session needs camera access to scan QR codes"; +"vc_scan_qr_code_grant_camera_access_button_title" = "Grant Camera Access"; +"vc_create_closed_group_title" = "Create Group"; +"vc_create_closed_group_text_field_hint" = "Enter a group name"; +"vc_create_closed_group_empty_state_message" = "You don't have any contacts yet"; +"vc_create_closed_group_group_name_missing_error" = "Please enter a group name"; +"vc_create_closed_group_group_name_too_long_error" = "Please enter a shorter group name"; +"vc_create_closed_group_too_many_group_members_error" = "A closed group cannot have more than 100 members"; +"vc_join_public_chat_title" = "Join Community"; +"vc_join_public_chat_enter_group_url_tab_title" = "Community URL"; +"vc_join_public_chat_scan_qr_code_tab_title" = "Scan QR Code"; +"vc_enter_chat_url_text_field_hint" = "Enter Community URL"; +"vc_settings_title" = "Settings"; +"vc_group_settings_title" = "Group Settings"; +"vc_settings_display_name_missing_error" = "Please pick a display name"; +"vc_settings_display_name_too_long_error" = "Please pick a shorter display name"; +"vc_settings_privacy_button_title" = "Privacy"; +"vc_settings_notifications_button_title" = "Notifications"; +"vc_settings_recovery_phrase_button_title" = "Recovery Phrase"; +"vc_settings_clear_all_data_button_title" = "Clear Data"; +"vc_qr_code_title" = "QR Code"; +"vc_qr_code_view_my_qr_code_tab_title" = "View My QR Code"; +"vc_qr_code_view_scan_qr_code_tab_title" = "Scan QR Code"; +"vc_qr_code_view_scan_qr_code_explanation" = "Scan someone's QR code to start a conversation with them"; +"vc_view_my_qr_code_explanation" = "This is your QR code. Other users can scan it to start a session with you."; +// MARK: - Not Yet Translated +"fast_mode_explanation" = "You’ll be notified of new messages reliably and immediately using Apple’s notification servers."; +"fast_mode" = "Fast Mode"; +"slow_mode_explanation" = "Session will occasionally check for new messages in the background."; +"slow_mode" = "Slow Mode"; +"vc_pn_mode_title" = "Message Notifications"; +"vc_link_device_recovery_phrase_tab_title" = "Recovery Phrase"; +"vc_link_device_scan_qr_code_explanation" = "Navigate to Settings → Recovery Phrase on your other device to show your QR code."; +"vc_enter_recovery_phrase_title" = "Recovery Phrase"; +"vc_enter_recovery_phrase_explanation" = "To link your device, enter the recovery phrase that was given to you when you signed up."; +"vc_enter_public_key_text_field_hint" = "Enter Session ID or ONS name"; +"admin_group_leave_warning" = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone."; +"vc_join_open_group_suggestions_title" = "Or join one of these..."; +"vc_settings_invite_a_friend_button_title" = "Invite a Friend"; +"copied" = "Copied"; +"vc_conversation_settings_copy_session_id_button_title" = "Copy Session ID"; +"vc_conversation_input_prompt" = "Message"; +"vc_conversation_voice_message_cancel_message" = "Slide to Cancel"; +"modal_download_attachment_title" = "Trust %@?"; +"modal_download_attachment_explanation" = "Are you sure you want to download media sent by %@?"; +"modal_download_button_title" = "Download"; +"modal_open_url_title" = "Open URL?"; +"modal_open_url_explanation" = "Are you sure you want to open %@?"; +"modal_open_url_button_title" = "Open"; +"modal_copy_url_button_title" = "Copy Link"; +"modal_blocked_title" = "Unblock %@?"; +"modal_blocked_explanation" = "Are you sure you want to unblock %@?"; +"modal_blocked_button_title" = "Unblock"; +"modal_link_previews_title" = "Enable Link Previews?"; +"modal_link_previews_explanation" = "Enabling link previews will show previews for URLs you send and receive. This can be useful, but Session will need to contact linked websites to generate previews. You can always disable link previews in Session's settings."; +"modal_link_previews_button_title" = "Enable"; +"vc_share_title" = "Share to Session"; +"vc_share_loading_message" = "Preparing attachments..."; +"vc_share_sending_message" = "Sending..."; +"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link"; +"vc_share_link_previews_error" = "Unable to load preview"; +"vc_share_link_previews_disabled_title" = "Link Previews Disabled"; +"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings."; +"view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; +"modal_send_seed_title" = "Warning"; +"modal_send_seed_explanation" = "This is your recovery phrase. If you send it to someone they'll have full access to your account."; +"modal_send_seed_send_button_title" = "Send"; +"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only"; +"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you."; +"view_conversation_title_notify_for_mentions_only" = "Апавяшчэнне толькі для згадак"; +"message_deleted" = "Гэта паведамленне было выдалена"; +"delete_message_for_me" = "Выдаліць толькі для мяне"; +"delete_message_for_everyone" = "Выдаліць для ўсіх"; +"delete_message_for_me_and_recipient" = "Выдаліць для мяне і %@"; +"context_menu_reply" = "Адказаць"; +"context_menu_save" = "Захаваць"; +"context_menu_ban_user" = "Забараніць карыстальніка"; +"context_menu_ban_and_delete_all" = "Забараніць і выдаліць усе"; +"context_menu_ban_user_error_alert_message" = "Unable to ban user"; +"accessibility_expanding_attachments_button" = "Дадайце ўкладанні"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Дакумент"; +"accessibility_library_button" = "Фотагалерэя"; +"accessibility_camera_button" = "Камера"; +"accessibility_main_button_collapse" = "Згарнуць параметры далучэння"; +"invalid_recovery_phrase" = "Няправільная фраза аднаўлення"; +"DISMISS_BUTTON_TEXT" = "Адхіліць"; +/* Button text which opens the settings app */ +"OPEN_SETTINGS_BUTTON" = "Налады"; +"call_outgoing" = "Вы патэлефанавалі"; +"call_incoming" = "%@ тэлефанаваў(ла) Вам"; +"call_missed" = "Missed Call from %@"; +"APN_Message" = "You've got a new message."; +"APN_Collapsed_Messages" = "You've got %@ new messages."; +"PIN_BUTTON_TEXT" = "Pin"; +"UNPIN_BUTTON_TEXT" = "Unpin"; +"modal_call_missed_tips_title" = "Call missed"; +"modal_call_missed_tips_explanation" = "Call missed from '%@' because you needed to enable the 'Voice and video calls' permission in the Privacy Settings."; +"media_saved" = "Media saved by %@."; +"screenshot_taken" = "%@ took a screenshot."; +"SEARCH_SECTION_CONTACTS" = "Contacts & Groups"; +"SEARCH_SECTION_MESSAGES" = "Паведамленне"; +"MESSAGE_REQUESTS_TITLE" = "Запыты паведамленняў"; +"MESSAGE_REQUESTS_EMPTY_TEXT" = "Няма чаканых запытаў паведамленняў"; +"MESSAGE_REQUESTS_CLEAR_ALL" = "Ачысціць усё"; +"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Вы ўпэўнены, што хочаце ачысціць усе запыты паведамленняў?"; +"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Ачысьціць"; +"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Вы ўпэўнены, што жадаеце выдаліць гэты запыт на паведамленне?"; +"MESSAGE_REQUESTS_BLOCK_CONFIRMATION_ACTON" = "Are you sure you want to block this contact?"; +"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request and reveal your Session ID."; +"MESSAGE_REQUESTS_ACCEPTED" = "Ваш запыт на паведамленне быў прыняты."; +"MESSAGE_REQUESTS_NOTIFICATION" = "У вас ёсць запыт на новае паведамленне"; +"TXT_HIDE_TITLE" = "Схаваць"; +"TXT_DELETE_ACCEPT" = "Прыняць"; +"TXT_BLOCK_USER_TITLE" = "Block User"; +"ALERT_ERROR_TITLE" = "Error"; +"modal_call_permission_request_title" = "Неабходныя дазволы на выклік"; +"modal_call_permission_request_explanation" = "Вы можаце ўключыць дазвол «Галасавыя і відэазванкі» у наладах прыватнасці."; +"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; +"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; +"LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; +"RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; +"RECOVERY_PHASE_ERROR_LAST_WORD" = "You seem to be missing the last word of your recovery phrase. Please check what you entered and try again."; +"RECOVERY_PHASE_ERROR_INVALID_WORD" = "There appears to be an invalid word in your recovery phrase. Please check what you entered and try again."; +"RECOVERY_PHASE_ERROR_FAILED" = "Your recovery phrase couldn't be verified. Please check what you entered and try again."; +/* Indicates that an unknown error occurred while using Touch ID/Face ID/Phone Passcode. */ +"SCREEN_LOCK_ENABLE_UNKNOWN_ERROR" = "Authentication could not be accessed."; +/* Indicates that Touch ID/Face ID/Phone Passcode authentication failed. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_FAILED" = "Authentication failed."; +/* Indicates that Touch ID/Face ID/Phone Passcode is 'locked out' on this device due to authentication failures. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_LOCKOUT" = "Too many failed authentication attempts. Please try again later."; +/* Indicates that Touch ID/Face ID/Phone Passcode are not available on this device. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE" = "You must enable a passcode in your iOS Settings in order to use Screen Lock."; +/* Indicates that Touch ID/Face ID/Phone Passcode is not configured on this device. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_ENROLLED" = "You must enable a passcode in your iOS Settings in order to use Screen Lock."; +/* Indicates that Touch ID/Face ID/Phone Passcode passcode is not set. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_PASSCODE_NOT_SET" = "You must enable a passcode in your iOS Settings in order to use Screen Lock."; +/* Label for the button to send a message */ +"SEND_BUTTON_TITLE" = "Send"; +/* Generic text for button that retries whatever the last action was. */ +"RETRY_BUTTON_TEXT" = "Retry"; +/* notification action */ +"SHOW_THREAD_BUTTON_TITLE" = "Show Chat"; +/* notification body */ +"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; +"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; +"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"MEDIA_TAB_TITLE" = "Media"; +"DOCUMENT_TAB_TITLE" = "Documents"; +"DOCUMENT_TILES_EMPTY_DOCUMENT" = "You don't have any document in this conversation."; +"DOCUMENT_TILES_LOADING_MORE_RECENT_LABEL" = "Loading Newer Document…"; +"DOCUMENT_TILES_LOADING_OLDER_LABEL" = "Loading Older Document…"; +/* The name for the emoji category 'Activities' */ +"EMOJI_CATEGORY_ACTIVITIES_NAME" = "Activities"; +/* The name for the emoji category 'Animals & Nature' */ +"EMOJI_CATEGORY_ANIMALS_NAME" = "Animals & Nature"; +/* The name for the emoji category 'Flags' */ +"EMOJI_CATEGORY_FLAGS_NAME" = "Flags"; +/* The name for the emoji category 'Food & Drink' */ +"EMOJI_CATEGORY_FOOD_NAME" = "Food & Drink"; +/* The name for the emoji category 'Objects' */ +"EMOJI_CATEGORY_OBJECTS_NAME" = "Objects"; +/* The name for the emoji category 'Recents' */ +"EMOJI_CATEGORY_RECENTS_NAME" = "Recently Used"; +/* The name for the emoji category 'Smileys & People' */ +"EMOJI_CATEGORY_SMILEYSANDPEOPLE_NAME" = "Smileys & People"; +/* The name for the emoji category 'Symbols' */ +"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols"; +/* The name for the emoji category 'Travel & Places' */ +"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places"; +"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@."; +"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message."; +"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message."; +"EMOJI_REACTS_RATE_LIMIT_TOAST" = "Slow down! You've sent too many emoji reacts. Try again soon."; +/* New conversation screen*/ +"vc_new_conversation_title" = "New Conversation"; +"CREATE_GROUP_BUTTON_TITLE" = "Create"; +"JOIN_COMMUNITY_BUTTON_TITLE" = "Join"; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one conversations."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; +"NOTIFICATIONS_TITLE" = "Notifications"; +"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; +"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; +"NOTIFICATIONS_STRATEGY_FAST_MODE_DESCRIPTION" = "You'll be notified of new message reliably and immediately using Apple's notification servers."; +"NOTIFICATIONS_STRATEGY_FAST_MODE_ACTION" = "Go to device notification settings"; +"NOTIFICATIONS_SECTION_STYLE" = "Notification Style"; +"NOTIFICATIONS_STYLE_SOUND_TITLE" = "Sound"; +"NOTIFICATIONS_STYLE_SOUND_WHEN_OPEN_TITLE" = "Sound When App is Open"; +"NOTIFICATIONS_STYLE_CONTENT_TITLE" = "Notification Content"; +"NOTIFICATIONS_STYLE_CONTENT_DESCRIPTION" = "The information shown in notifications."; +"NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT" = "Name & Content"; +"NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_ONLY" = "Name Only"; +"NOTIFICATIONS_STYLE_CONTENT_OPTION_NO_NAME_OR_CONTENT" = "No Name or Content"; +"CONVERSATION_SETTINGS_TITLE" = "Conversations"; +"CONVERSATION_SETTINGS_SECTION_MESSAGE_TRIMMING" = "Message Trimming"; +"CONVERSATION_SETTINGS_MESSAGE_TRIMMING_TITLE" = "Trim Communities"; +"CONVERSATION_SETTINGS_MESSAGE_TRIMMING_DESCRIPTION" = "Delete messages older than 6 months from Communities that have over 2,000 messages."; +"CONVERSATION_SETTINGS_SECTION_AUDIO_MESSAGES" = "Audio Messages"; +"CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_TITLE" = "Autoplay Audio Messages"; +"CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_DESCRIPTION" = "Autoplay consecutive audio messages."; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_TITLE" = "Blocked Contacts"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_EMPTY_STATE" = "You have no blocked contacts."; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK" = "Unblock"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_SINGLE" = "Are you sure you want to unblock %@?"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_FALLBACK" = "this contact"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_1" = "Are you sure you want to unblock %@"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_2_SINGLE" = "and %@?"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_3" = "and %d others?"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_ACTON" = "Unblock"; +"APPEARANCE_TITLE" = "Appearance"; +"APPEARANCE_THEMES_TITLE" = "Themes"; +"APPEARANCE_PRIMARY_COLOR_TITLE" = "Primary colour"; +"APPEARANCE_PRIMARY_COLOR_PREVIEW_INC_QUOTE" = "How are you?"; +"APPEARANCE_PRIMARY_COLOR_PREVIEW_INC_MESSAGE" = "I'm good thanks, you?"; +"APPEARANCE_PRIMARY_COLOR_PREVIEW_OUT_MESSAGE" = "I'm doing great, thanks."; +"APPEARANCE_NIGHT_MODE_TITLE" = "Auto night-mode"; +"APPEARANCE_NIGHT_MODE_TOGGLE" = "Match system settings"; +"HELP_TITLE" = "Help"; +"HELP_REPORT_BUG_TITLE" = "Report a Bug"; +"HELP_REPORT_BUG_DESCRIPTION" = "Export your logs, then upload the file though Session's Help Desk."; +"HELP_REPORT_BUG_ACTION_TITLE" = "Export Logs"; +"HELP_TRANSLATE_TITLE" = "Translate Session"; +"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; +"HELP_FAQ_TITLE" = "FAQ"; +"HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Clear All Data"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; +"modal_clear_all_data_confirm" = "Clear"; +"modal_seed_title" = "Your Recovery Phrase"; +"modal_seed_explanation" = "You can use your recovery phrase to restore your account or link a device."; +"modal_permission_explanation" = "Session needs %@ access to continue. You can enable access in the iOS settings."; +"modal_permission_settings_title" = "Settings"; +"modal_permission_camera" = "camera"; +"modal_permission_microphone" = "microphone"; +"modal_permission_library" = "library"; +"DISAPPEARING_MESSAGES_OFF" = "Off"; +"DISAPPEARING_MESSAGES_SUBTITLE_OFF" = "Off"; +"DISAPPEARING_MESSAGES_SUBTITLE_DISAPPEAR_AFTER" = "Disappear After: %@"; +"COPY_GROUP_URL" = "Copy Group URL"; +"NEW_CONVERSATION_CONTACTS_SECTION_TITLE" = "Contacts"; +"GROUP_ERROR_NO_MEMBER_SELECTION" = "Please pick at least 1 group member"; +"GROUP_CREATION_PLEASE_WAIT" = "Please wait while the group is created..."; +"GROUP_CREATION_ERROR_TITLE" = "Couldn't Create Group"; +"GROUP_CREATION_ERROR_MESSAGE" = "Please check your internet connection and try again."; +"GROUP_UPDATE_ERROR_TITLE" = "Couldn't Update Group"; +"GROUP_UPDATE_ERROR_MESSAGE" = "Can't leave while adding or removing other members."; +"GROUP_ACTION_REMOVE" = "Remove"; +"GROUP_TITLE_MEMBERS" = "Members"; +"GROUP_TITLE_FALLBACK" = "Group"; +"DM_ERROR_DIRECT_BLINDED_ID" = "You can only send messages to Blinded IDs from within a Community"; +"DM_ERROR_INVALID" = "Please check the Session ID or ONS name and try again"; +"COMMUNITY_ERROR_INVALID_URL" = "Please check the URL you entered and try again."; +"COMMUNITY_ERROR_GENERIC" = "Couldn't Join"; +"DISAPPERING_MESSAGES_TITLE" = "Disappearing Messages"; +"DISAPPERING_MESSAGES_TYPE_TITLE" = "Delete Type"; +"DISAPPERING_MESSAGES_TYPE_AFTER_READ_TITLE" = "Disappear After Read"; +"DISAPPERING_MESSAGES_TYPE_AFTER_READ_DESCRIPTION" = "Messages delete after they have been read."; +"DISAPPERING_MESSAGES_TYPE_AFTER_SEND_TITLE" = "Disappear After Send"; +"DISAPPERING_MESSAGES_TYPE_AFTER_SEND_DESCRIPTION" = "Messages delete after they have been sent."; +"DISAPPERING_MESSAGES_TIMER_TITLE" = "Timer"; +"DISAPPERING_MESSAGES_SAVE_TITLE" = "Set"; +"DISAPPERING_MESSAGES_GROUP_WARNING" = "This setting applies to everyone in this conversation."; +"DISAPPERING_MESSAGES_GROUP_WARNING_ADMIN_ONLY" = "This setting applies to everyone in this conversation. Only group admins can change this setting."; +"DISAPPERING_MESSAGES_SUMMARY" = "Disappear After %@ - %@"; +"DISAPPERING_MESSAGES_INFO_ENABLE" = "%@ has set messages to disappear %@ after they have been %@"; +"DISAPPERING_MESSAGES_INFO_UPDATE" = "%@ has changed messages to disappear %@ after they have been %@"; +"DISAPPERING_MESSAGES_INFO_DISABLE" = "%@ has turned off disappearing messages"; + +/* context_menu_info */ +"context_menu_info" = "Info"; + +/* An error that is displayed when the application fails for create it's initial connection to the database */ +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; + +/* A warning displayed to the user when the application takes too long to launch */ +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; + +/* The title of a button on a modal shown when the application fails to start, pressing the button closes the application */ +"APP_STARTUP_EXIT" = "Exit"; + +/* An error which occurs if the user tries to restore the database after an initial failure and it fails to restore */ +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; + +/* Text displayed in place of a quoted message when the original message is not on the device */ +"QUOTED_MESSAGE_NOT_FOUND" = "Original message not found."; + +/* EMOJI_REACTS_SHOW_LESS */ +"EMOJI_REACTS_SHOW_LESS" = "Show less"; + +/* PRIVACY_SECTION_MESSAGE_REQUESTS */ +"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests"; + +/* PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE */ +"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests"; + +/* PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION */ +"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations."; + +/* Information displayed above the input when sending a message to a new user for the first time explaining limitations around the types of messages which can be sent before being approved */ +"MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; + +/* State of a message while it's still in the process of being sent */ +"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending"; + +/* State of a message once it has been sent */ +"MESSAGE_DELIVERY_STATUS_SENT" = "Sent"; + +/* State of a message after the recipient has read the message */ +"MESSAGE_DELIVERY_STATUS_READ" = "Read"; + +/* State of a message if it failed to be sent */ +"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; + +/* Title of the message information screen describing the date/time a message was sent */ +"MESSAGE_INFO_SENT" = "Sent"; + +/* Title of the message information screen describing the date/time a message was received on a specific device */ +"MESSAGE_INFO_RECEIVED" = "Received"; + +/* Title of the message information screen describing the sender of the message */ +"MESSAGE_INFO_FROM" = "From"; + +/* Title of the message information screen describing the identifier of the attachment */ +"ATTACHMENT_INFO_FILE_ID" = "File ID"; + +/* Title of the message information screen describing the file type of the attachment */ +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; + +/* Title of the message information screen describing the size of the attachment */ +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; + +/* Title on the message information screen describing the resolution of a media attachment */ +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; + +/* Title on the message information screen describing the duration of a media attachment */ +"ATTACHMENT_INFO_DURATION" = "Duration"; + +/* State of a message after it failed to sync to the current users other devices */ +"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync"; + +/* State of a message while it's in the process of being synced to the users other devices */ +"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing"; + +/* Title of the modal that appears after a user taps on the state of a message which failed to send */ +"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message"; + +/* Title of the modal that appears after a user taps on the state of a message which failed to sync to the users other devices */ +"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices"; + +/* Action for the modal shown when asking the user whether they want to delete from all of their devices */ +"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; + +/* Action in the long-press menu to trigger a message to be sent again after it has failed */ +"context_menu_resend" = "Resend"; + +/* Action in the long-press menu to trigger a message to be synced again after it has failed */ +"context_menu_resync" = "Resync"; + +/* Title of a modal show the first time a user tries to search for GIFs */ +"GIPHY_PERMISSION_TITLE" = "Search GIFs?"; + +/* Message of a modal show the first time a user tries to search for GIFs */ +"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; + +/* Action in the long-press menu to view more information about a specific message */ +"message_info_title" = "Message Info"; + +/* Action to mute a conversation in the swipe menu */ +"mute_button_text" = "Mute"; + +/* Action in the swipe menu to unmute a conversation */ +"unmute_button_text" = "Unmute"; + +/* Action in the swipe menu to mark a conversation as read */ +"MARK_AS_READ" = "Mark read"; + +/* Action in the swipe menu to mark a conversation as unread */ +"MARK_AS_UNREAD" = "Mark unread"; + +/* Title of the confirmation modal show when attempting to leave a group conversation */ +"leave_group_confirmation_alert_title" = "Leave Group"; + +/* Title of the confirmation modal show when attempting to leave a community conversation */ +"leave_community_confirmation_alert_title" = "Leave Community"; + +/* Message in the confirmation modal when leaving a community conversation */ +"leave_community_confirmation_alert_message" = "Are you sure you want to leave %@?"; + +/* Conversation subtitle while the user in the process of leaving */ +"group_you_leaving" = "Leaving..."; + +/* Conversation subtitle if the user in the failed to leave */ +"group_leave_error" = "Failed to leave Group!"; + +/* Message within a conversation indicating the device was unable to leave a group conversation */ +"group_unable_to_leave" = "Unable to leave the Group, please try again"; + +/* Title in the confirmation modal to delete a group */ +"delete_group_confirmation_alert_title" = "Delete Group"; + +/* Message in the confirmation modal to delete a group */ +"delete_group_confirmation_alert_message" = "Are you sure you want to delete %@?"; + +/* Title in the confirmation modal when the user tries to delete a one-to-one conversation */ +"delete_conversation_confirmation_alert_title" = "Delete Conversation"; + +/* Message in the confirmation modal when the user tries to delete a one-to-one conversation */ +"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; + +/* Title in the confirmation modal when the user tries to hide the 'Note to Self' conversation */ +"hide_note_to_self_confirmation_alert_title" = "Hide Note to Self"; + +/* Message in the confirmation modal when the user tries to hide the 'Note to Self' conversation */ +"hide_note_to_self_confirmation_alert_message" = "Are you sure you want to hide %@?"; + +/* Title in the modal for updating the users profile display picture */ +"update_profile_modal_title" = "Set Display Picture"; + +/* Save action in the modal for updating the users profile display picture */ +"update_profile_modal_save" = "Save"; + +/* Remove action in the modal for updating the users profile display picture */ +"update_profile_modal_remove" = "Remove"; + +/* Title for the error when failing to remove the users profile display picture */ +"update_profile_modal_remove_error_title" = "Unable to remove avatar image"; + +/* Title for the error when the user selects a profile display picture that is too large */ +"update_profile_modal_max_size_error_title" = "Maximum File Size Exceeded"; + +/* Message for the error when the user selects a profile display picture that is too large */ +"update_profile_modal_max_size_error_message" = "Please select a smaller photo and try again"; + +/* Title for the error when the user fails to update their profile display picture */ +"update_profile_modal_error_title" = "Couldn't Update Profile"; + +/* Message for the error when the user fails to update their profile display picture */ +"update_profile_modal_error_message" = "Please check your internet connection and try again"; + +/* Placeholder when entering a nickname for a contact */ +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; + +/* The separator within a conversation indicating that following messages are unread */ +"UNREAD_MESSAGES" = "Unread Messages"; + +/* Empty state for a conversation */ +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; + +/* Empty state for a read-only conversation */ +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; + +/* Empty state for the 'Note to Self' conversation */ +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; + +/* Message to indicate a user has Community Message Requests disabled */ +"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message."; + +/* Warning to indicate one of the users devices is running an old version of Session */ +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; + +/* Ann error displayed if the device is unable to retrieve the users recovery password */ +"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue."; + +/* An error displayed when trying to send a message if the device is unable to save it to the database */ +"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages."; + +/* An error indicating that the device was unable to access the database for some reason */ +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/bg.lproj/Localizable.strings b/Session/Meta/Translations/bg.lproj/Localizable.strings new file mode 100644 index 000000000..8be9e35ca --- /dev/null +++ b/Session/Meta/Translations/bg.lproj/Localizable.strings @@ -0,0 +1,783 @@ +/* No comment provided by engineer. */ +"ATTACHMENT" = "Прикачени"; +/* Title for 'caption' mode of the attachment approval view. */ +"ATTACHMENT_APPROVAL_CAPTION_TITLE" = "Надпис"; +/* Format string for file extension label in call interstitial view */ +"ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT" = "Тип файл: %@"; +/* Format string for file size label in call interstitial view. Embeds: {{file size as 'N mb' or 'N kb'}}. */ +"ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT" = "Големина:%@"; +/* One-line label indicating the user can add no more text to the media message field. */ +"ATTACHMENT_APPROVAL_MESSAGE_LENGTH_LIMIT_REACHED" = "Това е пределът на едно съобщение"; +/* Label for 'send' button in the 'attachment approval' dialog. */ +"ATTACHMENT_APPROVAL_SEND_BUTTON" = "Изпрати"; +/* Generic filename for an attachment with no known name */ +"ATTACHMENT_DEFAULT_FILENAME" = "Прикачен файл"; +/* The title of the 'attachment error' alert. */ +"ATTACHMENT_ERROR_ALERT_TITLE" = "Грешка при прикачването на файл"; +/* Attachment error message for image attachments which could not be converted to JPEG */ +"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Изображението неможе да се преобразува"; +/* Attachment error message for video attachments which could not be converted to MP4 */ +"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Грешка при прикрепянето на видеофайла. Файлът не може да бъде конвертиран във формат MP4."; +/* Attachment error message for image attachments which cannot be parsed */ +"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Изображението не може да бъде анализирано."; +/* Attachment error message for image attachments in which metadata could not be removed */ +"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Не можахме да премахнем метаданните на файла."; +/* Attachment error message for image attachments which could not be resized */ +"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Не можахме да преоразмерим изображението."; +/* Attachment error message for attachments whose data exceed file size limits */ +"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Размерът на файла е твърде голям."; +/* Attachment error message for attachments with invalid data */ +"ATTACHMENT_ERROR_INVALID_DATA" = "Файлът съдържа невалидни или неправилни данни."; +/* Attachment error message for attachments with an invalid file format */ +"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Неправилен формат на файла."; +/* Attachment error message for attachments without any data */ +"ATTACHMENT_ERROR_MISSING_DATA" = "Прикаченият файл е празен. Липсват данни."; +/* Alert title when picking a document fails for an unknown reason */ +"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Провалено избиране на документ."; +/* Alert body when picking a document fails because user picked a directory/bundle */ +"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_BODY" = "Моля, пробвайте да изпратите този файл или папка(директория) под формата на архив!"; +/* Alert title when picking a document fails because user picked a directory/bundle */ +"ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_TITLE" = "Не се поддържа този формат"; +/* Short text label for a voice message attachment, used for thread preview and on the lock screen */ +"ATTACHMENT_TYPE_VOICE_MESSAGE" = "Гласово съобщение"; +/* Button label for the 'block' button */ +"BLOCK_LIST_BLOCK_BUTTON" = "Блокиране"; +/* A format for the 'block user' action sheet title. Embeds {{the blocked user's name or phone number}}. */ +"BLOCK_LIST_BLOCK_USER_TITLE_FORMAT" = "Блокирай%@?"; +/* A format for the 'unblock user' action sheet title. Embeds {{the unblocked user's name or phone number}}. */ +"BLOCK_LIST_UNBLOCK_TITLE_FORMAT" = "Разблокирай%@?"; +/* Button label for the 'unblock' button */ +"BLOCK_LIST_UNBLOCK_BUTTON" = "Разблокиране"; +/* The message format of the 'conversation blocked' alert. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT" = "%@Беше блокиран."; +/* The title of the 'user blocked' alert. */ +"BLOCK_LIST_VIEW_BLOCKED_ALERT_TITLE" = "Блокиран потребител"; +/* Alert title after unblocking a group or 1:1 chat. Embeds the {{conversation title}}. */ +"BLOCK_LIST_VIEW_UNBLOCKED_ALERT_TITLE_FORMAT" = "%@Беше разблокиран."; +/* An explanation of the consequences of blocking another user. */ +"BLOCK_USER_BEHAVIOR_EXPLANATION" = "Блокираните потребители няма да могат да Ви изпращат съобщения, или гласови повиквания."; +/* Label for generic done button. */ +"BUTTON_DONE" = "Завършено"; +/* Button text to enable batch selection mode */ +"BUTTON_SELECT" = "Избери"; +/* keyboard toolbar label when starting to search with no current results */ +"CONVERSATION_SEARCH_SEARCHING" = "Търсене..."; +/* keyboard toolbar label when no messages match the search string */ +"CONVERSATION_SEARCH_NO_RESULTS" = "Няма съвпадения"; +/* keyboard toolbar label when exactly 1 message matches the search string */ +"CONVERSATION_SEARCH_ONE_RESULT" = "1 съвпадение"; +/* keyboard toolbar label when more than 1 message matches the search string. Embeds {{number/position of the 'currently viewed' result}} and the {{total number of results}} */ +"CONVERSATION_SEARCH_RESULTS_FORMAT" = "%dот%d съвпадат"; +/* table cell label in conversation settings */ +"CONVERSATION_SETTINGS_BLOCK_THIS_USER" = "Блокирай този потребител"; +/* label for 'mute thread' cell in conversation settings */ +"CONVERSATION_SETTINGS_MUTE_LABEL" = "Заглуши"; +/* Table cell label in conversation settings which returns the user to the conversation with 'search mode' activated */ +"CONVERSATION_SETTINGS_SEARCH" = "Намери разговор"; +/* Title for the 'crop/scale image' dialog. */ +"CROP_SCALE_IMAGE_VIEW_TITLE" = "Премести или преоразмери"; +/* Subtitle shown while the app is updating its database. */ +"DATABASE_VIEW_OVERLAY_SUBTITLE" = "Това може да отнеме няколко минути."; +/* Title shown while the app is updating its database. */ +"DATABASE_VIEW_OVERLAY_TITLE" = "Опресняване на данните"; +/* The present; the current time. */ +"DATE_NOW" = "Сега"; +/* table cell label in conversation settings */ +"DISAPPEARING_MESSAGES" = "Изчезващи съобщения"; +/* table cell label in conversation settings */ +"EDIT_GROUP_ACTION" = "Редактирай участниците в групата"; +/* Label indicating media gallery is empty */ +"GALLERY_TILES_EMPTY_GALLERY" = "Вие нямате никакви файлове в този разговор."; +/* Label indicating loading is in progress */ +"GALLERY_TILES_LOADING_MORE_RECENT_LABEL" = "Зареждане на скорошни файлове…"; +/* Label indicating loading is in progress */ +"GALLERY_TILES_LOADING_OLDER_LABEL" = "Зареждане на по-стари файлове…"; +/* Error displayed when there is a failure fetching a GIF from the remote service. */ +"GIF_PICKER_ERROR_FETCH_FAILURE" = "Извличането на посочения GIF е неуспешно. +Моля, проверете интернет връзката си."; +/* Generic error displayed when picking a GIF */ +"GIF_PICKER_ERROR_GENERIC" = "Възникна неизвестна грешка."; +/* Shown when selected GIF couldn't be fetched */ +"GIF_PICKER_FAILURE_ALERT_TITLE" = "Невъзможно извличане на посочения GIF"; +/* Alert message shown when user tries to search for GIFs without entering any search terms. */ +"GIF_PICKER_VIEW_MISSING_QUERY" = "Моля въведете ключова дума за търсене."; +/* Indicates that an error occurred while searching. */ +"GIF_VIEW_SEARCH_ERROR" = "Възникна грешка. Моля натиснете за повторно търсене."; +/* Indicates that the user's search had no results. */ +"GIF_VIEW_SEARCH_NO_RESULTS" = "Няма съвпадения."; +/* No comment provided by engineer. */ +"GROUP_CREATED" = "Групата е създадена"; +/* No comment provided by engineer. */ +"GROUP_MEMBER_JOINED" = "%@ се присъедини към групата. "; +/* No comment provided by engineer. */ +"GROUP_MEMBER_LEFT" = "%@ напусна групата. "; +/* No comment provided by engineer. */ +"GROUP_MEMBER_REMOVED" = "%@ беше премахнат от групата. "; +/* No comment provided by engineer. */ +"GROUP_MEMBERS_REMOVED" = "%@ бяха премахнати от групата. "; +/* No comment provided by engineer. */ +"GROUP_TITLE_CHANGED" = "Името на групата е променено на '%@'. "; +/* No comment provided by engineer. */ +"GROUP_UPDATED" = "Групата е обновена."; +/* No comment provided by engineer. */ +"GROUP_YOU_LEFT" = "Вие напуснахте групата."; +/* No comment provided by engineer. */ +"YOU_WERE_REMOVED" = " Вие бяхте премахнат от групата. "; +/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */ +"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "Вие не можете да изберете повече от%@ елементи."; +/* alert title */ +"IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS" = "Фаилът не може да бъде прикачен."; +/* Message for the alert indicating that an audio file is invalid. */ +"INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE" = "Неподдържан формат на звуковия фаил."; +/* Confirmation button within contextual alert */ +"LEAVE_BUTTON_TITLE" = "Напускане"; +/* table cell label in conversation settings */ +"LEAVE_GROUP_ACTION" = "Напускане на групата"; +/* nav bar button item */ +"MEDIA_DETAIL_VIEW_ALL_MEDIA_BUTTON" = "Виж всички файлове"; +/* Confirmation button text to delete selected media from the gallery, embeds {{number of messages}} */ +"MEDIA_GALLERY_DELETE_MULTIPLE_MESSAGES_FORMAT" = "Изтрий %d съобщения"; +/* Confirmation button text to delete selected media message from the gallery */ +"MEDIA_GALLERY_DELETE_SINGLE_MESSAGE" = "Изтрий съобщението"; +/* embeds {{sender name}} and {{sent datetime}}, e.g. 'Sarah on 10/30/18, 3:29' */ +"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@на%@"; +/* Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}. */ +"MEDIA_GALLERY_MORE_ITEMS_FORMAT" = "добави%@"; +/* Short sender label for media sent by you */ +"MEDIA_GALLERY_SENDER_NAME_YOU" = "Ти"; +/* Section header in media gallery collection view */ +"MEDIA_GALLERY_THIS_MONTH_HEADER" = "Този Месец"; +/* status message for failed messages */ +"MESSAGE_STATUS_FAILED" = "Изпращането е неуспешно."; +/* status message for read messages */ +"MESSAGE_STATUS_READ" = "Прочетено"; +/* message status while message is sending. */ +"MESSAGE_STATUS_SENDING" = "Изпращане…"; +/* status message for sent messages */ +"MESSAGE_STATUS_SENT" = "Изпратено"; +/* status message while attachment is uploading */ +"MESSAGE_STATUS_UPLOADING" = "Прикачване…"; +/* notification title. Embeds {{author name}} and {{group name}} */ +"NEW_GROUP_MESSAGE_NOTIFICATION_TITLE" = "%@от%@"; +/* Label for 1:1 conversation with yourself. */ +"NOTE_TO_SELF" = "Лична бележка"; +/* Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone') */ +"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT" = "Възможно е да сте получили съобщения докато сте рестартирали вашият %@."; +/* No comment provided by engineer. */ +"BUTTON_OK" = "Добре"; +/* Info Message when {{other user}} disables or doesn't support disappearing messages */ +"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ е деактивирал или не поддържа изчезващи съобщения."; +/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */ +"OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ настрои таймера за изчезващи съобщения на%@"; +/* alert title, generic error preventing user from capturing a photo */ +"PHOTO_CAPTURE_GENERIC_ERROR" = "Снимката не беше направена."; +/* alert title */ +"PHOTO_CAPTURE_UNABLE_TO_CAPTURE_IMAGE" = "Възникна грешка при снимането."; +/* alert title */ +"PHOTO_CAPTURE_UNABLE_TO_INITIALIZE_CAMERA" = "Грешка в настройките на камерата."; +/* label for system photo collections which have no name. */ +"PHOTO_PICKER_UNNAMED_COLLECTION" = "Албум без име"; +/* Notification action button title */ +"PUSH_MANAGER_MARKREAD" = "Маркирай като прочетено"; +/* Notification action button title */ +"PUSH_MANAGER_REPLY" = "Отговори"; +/* Description of how and why Session iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'. */ +"SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK" = "Идентифицирайте се за да отключите Session."; +/* Title for alert indicating that screen lock could not be unlocked. */ +"SCREEN_LOCK_UNLOCK_FAILED" = "Възникна грешка при отключването"; +/* alert title when user attempts to leave the send media flow when they have an in-progress album */ +"SEND_MEDIA_ABANDON_TITLE" = "Отмяна на изпращането?"; +/* alert action, confirming the user wants to exit the media flow and abandon any photos they've taken */ +"SEND_MEDIA_CONFIRM_ABANDON_ALBUM" = "Потвърдете отмяната на изпращането"; +/* Format string for the default 'Note' sound. Embeds the system {{sound name}}. */ +"SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT" = "%@ (по подразбиране)"; +/* Label for settings view that allows user to change the notification sound. */ +"SETTINGS_ITEM_NOTIFICATION_SOUND" = "Тон на съобщенията"; +/* Label for the 'no sound' option that allows users to disable sounds for notifications, etc. */ +"SOUNDS_NONE" = "Без звук"; +/* {{number of days}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 days}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_DAYS" = "%@дни"; +/* Label text below navbar button, embeds {{number of days}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5d' not '5 d'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_DAYS_SHORT_FORMAT" = "%@д"; +/* {{number of hours}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 hours}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_HOURS" = "%@часа"; +/* Label text below navbar button, embeds {{number of hours}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5h' not '5 h'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_HOURS_SHORT_FORMAT" = "%@ч"; +/* {{number of minutes}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 minutes}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_MINUTES" = "%@минути"; +/* Label text below navbar button, embeds {{number of minutes}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5m' not '5 m'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_MINUTES_SHORT_FORMAT" = "%@м"; +/* {{number of seconds}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 seconds}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SECONDS" = "%@секунди"; +/* Label text below navbar button, embeds {{number of seconds}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5s' not '5 s'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SECONDS_SHORT_FORMAT" = "%@с"; +/* {{1 day}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 day}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_DAY" = "%@ден"; +/* {{1 hour}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 hour}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_HOUR" = "%@час"; +/* {{1 minute}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 minute}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_MINUTE" = "%@минута"; +/* {{1 week}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{1 week}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_SINGLE_WEEK" = "%@седмица"; +/* {{number of weeks}}, embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 weeks}}'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_WEEKS" = "%@седмици"; +/* Label text below navbar button, embeds {{number of weeks}}. Must be very short, like 1 or 2 characters, The space is intentionally omitted between the text and the embedded duration so that we get, e.g. '5w' not '5 w'. See other *_TIME_AMOUNT strings */ +"TIME_AMOUNT_WEEKS_SHORT_FORMAT" = "%@сц"; +/* Label for the cancel button in an alert or action sheet. */ +"TXT_CANCEL_TITLE" = "Анулиране"; +/* No comment provided by engineer. */ +"TXT_DELETE_TITLE" = "Изтриване"; +/* Filename for voice messages. */ +"VOICE_MESSAGE_FILE_NAME" = "Гласово съобщение"; +/* Message for the alert indicating the 'voice message' needs to be held to be held down to record. */ +"VOICE_MESSAGE_TOO_SHORT_ALERT_MESSAGE" = "Натиснете и задръжте за запис."; +/* Title for the alert indicating the 'voice message' needs to be held to be held down to record. */ +"VOICE_MESSAGE_TOO_SHORT_ALERT_TITLE" = "Задръжте по време на записването"; +/* Info Message when you disable disappearing messages */ +"YOU_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "Вие деактивирахте изчезващите съобщения."; +/* Info message embedding a {{time amount}}, see the *_TIME_AMOUNT strings for context. */ +"YOU_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "You set disappearing message time to %@"; +// MARK: - Session +"continue_2" = "Continue"; +"copy" = "Copy"; +"invalid_url" = "Invalid URL"; +"next" = "Next"; +"share" = "Share"; +"invalid_session_id" = "Invalid Session ID"; +"cancel" = "Cancel"; +"your_session_id" = "Your Session ID"; +"vc_landing_title_2" = "Your Session begins here..."; +"vc_landing_register_button_title" = "Create Session ID"; +"vc_landing_restore_button_title" = "Continue Your Session"; +"vc_landing_link_button_title" = "Link a Device"; +"view_fake_chat_bubble_1" = "What's Session?"; +"view_fake_chat_bubble_2" = "It's a decentralized, encrypted messaging app"; +"view_fake_chat_bubble_3" = "So it doesn't collect my personal information or my conversation metadata? How does it work?"; +"view_fake_chat_bubble_4" = "Using a combination of advanced anonymous routing and end-to-end encryption technologies."; +"view_fake_chat_bubble_5" = "Friends don't let friends use compromised messengers. You're welcome."; +"vc_register_title" = "Say hello to your Session ID"; +"vc_register_explanation" = "Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design."; +"vc_restore_title" = "Restore your account"; +"vc_restore_explanation" = "Enter the recovery phrase that was given to you when you signed up to restore your account."; +"vc_restore_seed_text_field_hint" = "Enter your recovery phrase"; +"vc_link_device_title" = "Link a Device"; +"vc_link_device_scan_qr_code_tab_title" = "Scan QR Code"; +"vc_display_name_title_2" = "Pick your display name"; +"vc_display_name_explanation" = "This will be your name when you use Session. It can be your real name, an alias, or anything else you like."; +"vc_display_name_text_field_hint" = "Enter a display name"; +"vc_display_name_display_name_missing_error" = "Please pick a display name"; +"vc_display_name_display_name_too_long_error" = "Please pick a shorter display name"; +"vc_pn_mode_recommended_option_tag" = "Recommended"; +"vc_pn_mode_no_option_picked_modal_title" = "Please Pick an Option"; +"vc_home_empty_state_message" = "You don't have any contacts yet"; +"vc_home_empty_state_button_title" = "Start a Session"; +"vc_seed_title" = "Your Recovery Phrase"; +"vc_seed_title_2" = "Meet your recovery phrase"; +"vc_seed_explanation" = "Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don’t give it to anyone."; +"vc_seed_reveal_button_title" = "Hold to reveal"; +"view_seed_reminder_subtitle_1" = "Secure your account by saving your recovery phrase"; +"view_seed_reminder_subtitle_2" = "Tap and hold the redacted words to reveal your recovery phrase, then store it safely to secure your Session ID."; +"view_seed_reminder_subtitle_3" = "Make sure to store your recovery phrase in a safe place"; +"vc_path_title" = "Path"; +"vc_path_explanation" = "Session hides your IP by routing your messages through multiple Service Nodes in Session's decentralized network. These are the countries your connection is currently being routed through:"; +"vc_path_device_row_title" = "Ти"; +"vc_path_guard_node_row_title" = "Entry Node"; +"vc_path_service_node_row_title" = "Service Node"; +"vc_path_destination_row_title" = "Destination"; +"vc_path_learn_more_button_title" = "Научи повече"; +"vc_create_private_chat_title" = "New Message"; +"vc_create_private_chat_enter_session_id_tab_title" = "Enter Session ID"; +"vc_create_private_chat_scan_qr_code_tab_title" = "Scan QR Code"; +"vc_enter_public_key_explanation" = "Start a new conversation by entering someone's Session ID or share your Session ID with them."; +"vc_scan_qr_code_camera_access_explanation" = "Session needs camera access to scan QR codes"; +"vc_scan_qr_code_grant_camera_access_button_title" = "Grant Camera Access"; +"vc_create_closed_group_title" = "Create Group"; +"vc_create_closed_group_text_field_hint" = "Enter a group name"; +"vc_create_closed_group_empty_state_message" = "You don't have any contacts yet"; +"vc_create_closed_group_group_name_missing_error" = "Please enter a group name"; +"vc_create_closed_group_group_name_too_long_error" = "Please enter a shorter group name"; +"vc_create_closed_group_too_many_group_members_error" = "A closed group cannot have more than 100 members"; +"vc_join_public_chat_title" = "Join Community"; +"vc_join_public_chat_enter_group_url_tab_title" = "Community URL"; +"vc_join_public_chat_scan_qr_code_tab_title" = "Scan QR Code"; +"vc_enter_chat_url_text_field_hint" = "Enter Community URL"; +"vc_settings_title" = "Settings"; +"vc_group_settings_title" = "Group Settings"; +"vc_settings_display_name_missing_error" = "Please pick a display name"; +"vc_settings_display_name_too_long_error" = "Please pick a shorter display name"; +"vc_settings_privacy_button_title" = "Privacy"; +"vc_settings_notifications_button_title" = "Notifications"; +"vc_settings_recovery_phrase_button_title" = "Recovery Phrase"; +"vc_settings_clear_all_data_button_title" = "Clear Data"; +"vc_qr_code_title" = "QR Code"; +"vc_qr_code_view_my_qr_code_tab_title" = "View My QR Code"; +"vc_qr_code_view_scan_qr_code_tab_title" = "Scan QR Code"; +"vc_qr_code_view_scan_qr_code_explanation" = "Scan someone's QR code to start a conversation with them"; +"vc_view_my_qr_code_explanation" = "This is your QR code. Other users can scan it to start a session with you."; +// MARK: - Not Yet Translated +"fast_mode_explanation" = "You’ll be notified of new messages reliably and immediately using Apple’s notification servers."; +"fast_mode" = "Fast Mode"; +"slow_mode_explanation" = "Session will occasionally check for new messages in the background."; +"slow_mode" = "Slow Mode"; +"vc_pn_mode_title" = "Message Notifications"; +"vc_link_device_recovery_phrase_tab_title" = "Recovery Phrase"; +"vc_link_device_scan_qr_code_explanation" = "Navigate to Settings → Recovery Phrase on your other device to show your QR code."; +"vc_enter_recovery_phrase_title" = "Recovery Phrase"; +"vc_enter_recovery_phrase_explanation" = "To link your device, enter the recovery phrase that was given to you when you signed up."; +"vc_enter_public_key_text_field_hint" = "Enter Session ID or ONS name"; +"admin_group_leave_warning" = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone."; +"vc_join_open_group_suggestions_title" = "Or join one of these..."; +"vc_settings_invite_a_friend_button_title" = "Покани приятел"; +"copied" = "Copied"; +"vc_conversation_settings_copy_session_id_button_title" = "Copy Session ID"; +"vc_conversation_input_prompt" = "Message"; +"vc_conversation_voice_message_cancel_message" = "Slide to Cancel"; +"modal_download_attachment_title" = "Trust %@?"; +"modal_download_attachment_explanation" = "Are you sure you want to download media sent by %@?"; +"modal_download_button_title" = "Изтегляне"; +"modal_open_url_title" = "Open URL?"; +"modal_open_url_explanation" = "Are you sure you want to open %@?"; +"modal_open_url_button_title" = "Open"; +"modal_copy_url_button_title" = "Copy Link"; +"modal_blocked_title" = "Unblock %@?"; +"modal_blocked_explanation" = "Are you sure you want to unblock %@?"; +"modal_blocked_button_title" = "Unblock"; +"modal_link_previews_title" = "Enable Link Previews?"; +"modal_link_previews_explanation" = "Enabling link previews will show previews for URLs you send and receive. This can be useful, but Session will need to contact linked websites to generate previews. You can always disable link previews in Session's settings."; +"modal_link_previews_button_title" = "Enable"; +"vc_share_title" = "Share to Session"; +"vc_share_loading_message" = "Preparing attachments..."; +"vc_share_sending_message" = "Изпращане..."; +"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link"; +"vc_share_link_previews_error" = "Unable to load preview"; +"vc_share_link_previews_disabled_title" = "Link Previews Disabled"; +"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings."; +"view_open_group_invitation_description" = "Open group invitation"; +"vc_conversation_settings_invite_button_title" = "Add Members"; +"modal_send_seed_title" = "Warning"; +"modal_send_seed_explanation" = "This is your recovery phrase. If you send it to someone they'll have full access to your account."; +"modal_send_seed_send_button_title" = "Send"; +"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only"; +"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you."; +"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only"; +"message_deleted" = "This message has been deleted"; +"delete_message_for_me" = "Delete just for me"; +"delete_message_for_everyone" = "Delete for everyone"; +"delete_message_for_me_and_recipient" = "Delete for me and %@"; +"context_menu_reply" = "Reply"; +"context_menu_save" = "Save"; +"context_menu_ban_user" = "Ban User"; +"context_menu_ban_and_delete_all" = "Ban and Delete All"; +"context_menu_ban_user_error_alert_message" = "Unable to ban user"; +"accessibility_expanding_attachments_button" = "Add attachments"; +"accessibility_gif_button" = "Gif"; +"accessibility_document_button" = "Document"; +"accessibility_library_button" = "Photo library"; +"accessibility_camera_button" = "Камера"; +"accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; +"DISMISS_BUTTON_TEXT" = "Dismiss"; +/* Button text which opens the settings app */ +"OPEN_SETTINGS_BUTTON" = "Settings"; +"call_outgoing" = "You called %@"; +"call_incoming" = "%@ called you"; +"call_missed" = "Missed Call from %@"; +"APN_Message" = "You've got a new message."; +"APN_Collapsed_Messages" = "You've got %@ new messages."; +"PIN_BUTTON_TEXT" = "Pin"; +"UNPIN_BUTTON_TEXT" = "Unpin"; +"modal_call_missed_tips_title" = "Call missed"; +"modal_call_missed_tips_explanation" = "Call missed from '%@' because you needed to enable the 'Voice and video calls' permission in the Privacy Settings."; +"media_saved" = "Media saved by %@."; +"screenshot_taken" = "%@ took a screenshot."; +"SEARCH_SECTION_CONTACTS" = "Contacts & Groups"; +"SEARCH_SECTION_MESSAGES" = "Messages"; +"MESSAGE_REQUESTS_TITLE" = "Message Requests"; +"MESSAGE_REQUESTS_EMPTY_TEXT" = "No pending message requests"; +"MESSAGE_REQUESTS_CLEAR_ALL" = "Clear All"; +"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE" = "Are you sure you want to clear all message requests?"; +"MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON" = "Clear"; +"MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON" = "Are you sure you want to delete this message request?"; +"MESSAGE_REQUESTS_BLOCK_CONFIRMATION_ACTON" = "Are you sure you want to block this contact?"; +"MESSAGE_REQUESTS_INFO" = "Sending a message to this user will automatically accept their message request and reveal your Session ID."; +"MESSAGE_REQUESTS_ACCEPTED" = "Your message request has been accepted."; +"MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; +"TXT_HIDE_TITLE" = "Hide"; +"TXT_DELETE_ACCEPT" = "Accept"; +"TXT_BLOCK_USER_TITLE" = "Block User"; +"ALERT_ERROR_TITLE" = "Error"; +"modal_call_permission_request_title" = "Call Permissions Required"; +"modal_call_permission_request_explanation" = "You can enable the 'Voice and video calls' permission in the Privacy Settings."; +"DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; +"DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; +"LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; +"RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; +"RECOVERY_PHASE_ERROR_LAST_WORD" = "You seem to be missing the last word of your recovery phrase. Please check what you entered and try again."; +"RECOVERY_PHASE_ERROR_INVALID_WORD" = "There appears to be an invalid word in your recovery phrase. Please check what you entered and try again."; +"RECOVERY_PHASE_ERROR_FAILED" = "Your recovery phrase couldn't be verified. Please check what you entered and try again."; +/* Indicates that an unknown error occurred while using Touch ID/Face ID/Phone Passcode. */ +"SCREEN_LOCK_ENABLE_UNKNOWN_ERROR" = "Authentication could not be accessed."; +/* Indicates that Touch ID/Face ID/Phone Passcode authentication failed. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_FAILED" = "Authentication failed."; +/* Indicates that Touch ID/Face ID/Phone Passcode is 'locked out' on this device due to authentication failures. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_LOCKOUT" = "Too many failed authentication attempts. Please try again later."; +/* Indicates that Touch ID/Face ID/Phone Passcode are not available on this device. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE" = "You must enable a passcode in your iOS Settings in order to use Screen Lock."; +/* Indicates that Touch ID/Face ID/Phone Passcode is not configured on this device. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_ENROLLED" = "You must enable a passcode in your iOS Settings in order to use Screen Lock."; +/* Indicates that Touch ID/Face ID/Phone Passcode passcode is not set. */ +"SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_PASSCODE_NOT_SET" = "You must enable a passcode in your iOS Settings in order to use Screen Lock."; +/* Label for the button to send a message */ +"SEND_BUTTON_TITLE" = "Send"; +/* Generic text for button that retries whatever the last action was. */ +"RETRY_BUTTON_TEXT" = "Retry"; +/* notification action */ +"SHOW_THREAD_BUTTON_TITLE" = "Show Chat"; +/* notification body */ +"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; +"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; +"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"MEDIA_TAB_TITLE" = "Media"; +"DOCUMENT_TAB_TITLE" = "Documents"; +"DOCUMENT_TILES_EMPTY_DOCUMENT" = "You don't have any document in this conversation."; +"DOCUMENT_TILES_LOADING_MORE_RECENT_LABEL" = "Loading Newer Document…"; +"DOCUMENT_TILES_LOADING_OLDER_LABEL" = "Loading Older Document…"; +/* The name for the emoji category 'Activities' */ +"EMOJI_CATEGORY_ACTIVITIES_NAME" = "Activities"; +/* The name for the emoji category 'Animals & Nature' */ +"EMOJI_CATEGORY_ANIMALS_NAME" = "Animals & Nature"; +/* The name for the emoji category 'Flags' */ +"EMOJI_CATEGORY_FLAGS_NAME" = "Flags"; +/* The name for the emoji category 'Food & Drink' */ +"EMOJI_CATEGORY_FOOD_NAME" = "Food & Drink"; +/* The name for the emoji category 'Objects' */ +"EMOJI_CATEGORY_OBJECTS_NAME" = "Objects"; +/* The name for the emoji category 'Recents' */ +"EMOJI_CATEGORY_RECENTS_NAME" = "Recently Used"; +/* The name for the emoji category 'Smileys & People' */ +"EMOJI_CATEGORY_SMILEYSANDPEOPLE_NAME" = "Smileys & People"; +/* The name for the emoji category 'Symbols' */ +"EMOJI_CATEGORY_SYMBOLS_NAME" = "Symbols"; +/* The name for the emoji category 'Travel & Places' */ +"EMOJI_CATEGORY_TRAVEL_NAME" = "Travel & Places"; +"EMOJI_REACTS_NOTIFICATION" = "%@ reacts to a message with %@."; +"EMOJI_REACTS_MORE_REACTORS_ONE" = "And 1 other has reacted %@ to this message."; +"EMOJI_REACTS_MORE_REACTORS_MUTIPLE" = "And %@ others have reacted %@ to this message."; +"EMOJI_REACTS_RATE_LIMIT_TOAST" = "Slow down! You've sent too many emoji reacts. Try again soon."; +/* New conversation screen*/ +"vc_new_conversation_title" = "New Conversation"; +"CREATE_GROUP_BUTTON_TITLE" = "Create"; +"JOIN_COMMUNITY_BUTTON_TITLE" = "Join"; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one conversations."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; +"NOTIFICATIONS_TITLE" = "Notifications"; +"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; +"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; +"NOTIFICATIONS_STRATEGY_FAST_MODE_DESCRIPTION" = "You'll be notified of new message reliably and immediately using Apple's notification servers."; +"NOTIFICATIONS_STRATEGY_FAST_MODE_ACTION" = "Go to device notification settings"; +"NOTIFICATIONS_SECTION_STYLE" = "Notification Style"; +"NOTIFICATIONS_STYLE_SOUND_TITLE" = "Sound"; +"NOTIFICATIONS_STYLE_SOUND_WHEN_OPEN_TITLE" = "Sound When App is Open"; +"NOTIFICATIONS_STYLE_CONTENT_TITLE" = "Notification Content"; +"NOTIFICATIONS_STYLE_CONTENT_DESCRIPTION" = "The information shown in notifications."; +"NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT" = "Name & Content"; +"NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_ONLY" = "Name Only"; +"NOTIFICATIONS_STYLE_CONTENT_OPTION_NO_NAME_OR_CONTENT" = "No Name or Content"; +"CONVERSATION_SETTINGS_TITLE" = "Conversations"; +"CONVERSATION_SETTINGS_SECTION_MESSAGE_TRIMMING" = "Message Trimming"; +"CONVERSATION_SETTINGS_MESSAGE_TRIMMING_TITLE" = "Trim Communities"; +"CONVERSATION_SETTINGS_MESSAGE_TRIMMING_DESCRIPTION" = "Delete messages older than 6 months from Communities that have over 2,000 messages."; +"CONVERSATION_SETTINGS_SECTION_AUDIO_MESSAGES" = "Audio Messages"; +"CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_TITLE" = "Autoplay Audio Messages"; +"CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_DESCRIPTION" = "Autoplay consecutive audio messages."; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_TITLE" = "Blocked Contacts"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_EMPTY_STATE" = "You have no blocked contacts."; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK" = "Unblock"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_SINGLE" = "Are you sure you want to unblock %@?"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_FALLBACK" = "this contact"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_1" = "Are you sure you want to unblock %@"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_2_SINGLE" = "and %@?"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_3" = "and %d others?"; +"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_ACTON" = "Unblock"; +"APPEARANCE_TITLE" = "Appearance"; +"APPEARANCE_THEMES_TITLE" = "Themes"; +"APPEARANCE_PRIMARY_COLOR_TITLE" = "Primary colour"; +"APPEARANCE_PRIMARY_COLOR_PREVIEW_INC_QUOTE" = "How are you?"; +"APPEARANCE_PRIMARY_COLOR_PREVIEW_INC_MESSAGE" = "I'm good thanks, you?"; +"APPEARANCE_PRIMARY_COLOR_PREVIEW_OUT_MESSAGE" = "I'm doing great, thanks."; +"APPEARANCE_NIGHT_MODE_TITLE" = "Auto night-mode"; +"APPEARANCE_NIGHT_MODE_TOGGLE" = "Match system settings"; +"HELP_TITLE" = "Help"; +"HELP_REPORT_BUG_TITLE" = "Report a Bug"; +"HELP_REPORT_BUG_DESCRIPTION" = "Export your logs, then upload the file though Session's Help Desk."; +"HELP_REPORT_BUG_ACTION_TITLE" = "Export Logs"; +"HELP_TRANSLATE_TITLE" = "Translate Session"; +"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; +"HELP_FAQ_TITLE" = "FAQ"; +"HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Clear All Data"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; +"modal_clear_all_data_confirm" = "Clear"; +"modal_seed_title" = "Your Recovery Phrase"; +"modal_seed_explanation" = "You can use your recovery phrase to restore your account or link a device."; +"modal_permission_explanation" = "Session needs %@ access to continue. You can enable access in the iOS settings."; +"modal_permission_settings_title" = "Settings"; +"modal_permission_camera" = "camera"; +"modal_permission_microphone" = "microphone"; +"modal_permission_library" = "library"; +"DISAPPEARING_MESSAGES_OFF" = "Off"; +"DISAPPEARING_MESSAGES_SUBTITLE_OFF" = "Off"; +"DISAPPEARING_MESSAGES_SUBTITLE_DISAPPEAR_AFTER" = "Disappear After: %@"; +"COPY_GROUP_URL" = "Copy Group URL"; +"NEW_CONVERSATION_CONTACTS_SECTION_TITLE" = "Contacts"; +"GROUP_ERROR_NO_MEMBER_SELECTION" = "Please pick at least 1 group member"; +"GROUP_CREATION_PLEASE_WAIT" = "Please wait while the group is created..."; +"GROUP_CREATION_ERROR_TITLE" = "Couldn't Create Group"; +"GROUP_CREATION_ERROR_MESSAGE" = "Please check your internet connection and try again."; +"GROUP_UPDATE_ERROR_TITLE" = "Couldn't Update Group"; +"GROUP_UPDATE_ERROR_MESSAGE" = "Can't leave while adding or removing other members."; +"GROUP_ACTION_REMOVE" = "Remove"; +"GROUP_TITLE_MEMBERS" = "Members"; +"GROUP_TITLE_FALLBACK" = "Group"; +"DM_ERROR_DIRECT_BLINDED_ID" = "You can only send messages to Blinded IDs from within a Community"; +"DM_ERROR_INVALID" = "Please check the Session ID or ONS name and try again"; +"COMMUNITY_ERROR_INVALID_URL" = "Please check the URL you entered and try again."; +"COMMUNITY_ERROR_GENERIC" = "Couldn't Join"; +"DISAPPERING_MESSAGES_TITLE" = "Disappearing Messages"; +"DISAPPERING_MESSAGES_TYPE_TITLE" = "Delete Type"; +"DISAPPERING_MESSAGES_TYPE_AFTER_READ_TITLE" = "Disappear After Read"; +"DISAPPERING_MESSAGES_TYPE_AFTER_READ_DESCRIPTION" = "Messages delete after they have been read."; +"DISAPPERING_MESSAGES_TYPE_AFTER_SEND_TITLE" = "Disappear After Send"; +"DISAPPERING_MESSAGES_TYPE_AFTER_SEND_DESCRIPTION" = "Messages delete after they have been sent."; +"DISAPPERING_MESSAGES_TIMER_TITLE" = "Timer"; +"DISAPPERING_MESSAGES_SAVE_TITLE" = "Set"; +"DISAPPERING_MESSAGES_GROUP_WARNING" = "This setting applies to everyone in this conversation."; +"DISAPPERING_MESSAGES_GROUP_WARNING_ADMIN_ONLY" = "This setting applies to everyone in this conversation. Only group admins can change this setting."; +"DISAPPERING_MESSAGES_SUMMARY" = "Disappear After %@ - %@"; +"DISAPPERING_MESSAGES_INFO_ENABLE" = "%@ has set messages to disappear %@ after they have been %@"; +"DISAPPERING_MESSAGES_INFO_UPDATE" = "%@ has changed messages to disappear %@ after they have been %@"; +"DISAPPERING_MESSAGES_INFO_DISABLE" = "%@ has turned off disappearing messages"; + +/* context_menu_info */ +"context_menu_info" = "Info"; + +/* An error that is displayed when the application fails for create it's initial connection to the database */ +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; + +/* A warning displayed to the user when the application takes too long to launch */ +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; + +/* The title of a button on a modal shown when the application fails to start, pressing the button closes the application */ +"APP_STARTUP_EXIT" = "Exit"; + +/* An error which occurs if the user tries to restore the database after an initial failure and it fails to restore */ +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; + +/* Text displayed in place of a quoted message when the original message is not on the device */ +"QUOTED_MESSAGE_NOT_FOUND" = "Original message not found."; + +/* EMOJI_REACTS_SHOW_LESS */ +"EMOJI_REACTS_SHOW_LESS" = "Show less"; + +/* PRIVACY_SECTION_MESSAGE_REQUESTS */ +"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests"; + +/* PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE */ +"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests"; + +/* PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION */ +"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations."; + +/* Information displayed above the input when sending a message to a new user for the first time explaining limitations around the types of messages which can be sent before being approved */ +"MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; + +/* State of a message while it's still in the process of being sent */ +"MESSAGE_DELIVERY_STATUS_SENDING" = "Sending"; + +/* State of a message once it has been sent */ +"MESSAGE_DELIVERY_STATUS_SENT" = "Sent"; + +/* State of a message after the recipient has read the message */ +"MESSAGE_DELIVERY_STATUS_READ" = "Read"; + +/* State of a message if it failed to be sent */ +"MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; + +/* Title of the message information screen describing the date/time a message was sent */ +"MESSAGE_INFO_SENT" = "Sent"; + +/* Title of the message information screen describing the date/time a message was received on a specific device */ +"MESSAGE_INFO_RECEIVED" = "Received"; + +/* Title of the message information screen describing the sender of the message */ +"MESSAGE_INFO_FROM" = "From"; + +/* Title of the message information screen describing the identifier of the attachment */ +"ATTACHMENT_INFO_FILE_ID" = "File ID"; + +/* Title of the message information screen describing the file type of the attachment */ +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; + +/* Title of the message information screen describing the size of the attachment */ +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; + +/* Title on the message information screen describing the resolution of a media attachment */ +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; + +/* Title on the message information screen describing the duration of a media attachment */ +"ATTACHMENT_INFO_DURATION" = "Duration"; + +/* State of a message after it failed to sync to the current users other devices */ +"MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync"; + +/* State of a message while it's in the process of being synced to the users other devices */ +"MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing"; + +/* Title of the modal that appears after a user taps on the state of a message which failed to send */ +"MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message"; + +/* Title of the modal that appears after a user taps on the state of a message which failed to sync to the users other devices */ +"MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices"; + +/* Action for the modal shown when asking the user whether they want to delete from all of their devices */ +"delete_message_for_me_and_my_devices" = "Delete from all of my devices"; + +/* Action in the long-press menu to trigger a message to be sent again after it has failed */ +"context_menu_resend" = "Resend"; + +/* Action in the long-press menu to trigger a message to be synced again after it has failed */ +"context_menu_resync" = "Resync"; + +/* Title of a modal show the first time a user tries to search for GIFs */ +"GIPHY_PERMISSION_TITLE" = "Search GIFs?"; + +/* Message of a modal show the first time a user tries to search for GIFs */ +"GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; + +/* Action in the long-press menu to view more information about a specific message */ +"message_info_title" = "Message Info"; + +/* Action to mute a conversation in the swipe menu */ +"mute_button_text" = "Mute"; + +/* Action in the swipe menu to unmute a conversation */ +"unmute_button_text" = "Unmute"; + +/* Action in the swipe menu to mark a conversation as read */ +"MARK_AS_READ" = "Mark read"; + +/* Action in the swipe menu to mark a conversation as unread */ +"MARK_AS_UNREAD" = "Mark unread"; + +/* Title of the confirmation modal show when attempting to leave a group conversation */ +"leave_group_confirmation_alert_title" = "Leave Group"; + +/* Title of the confirmation modal show when attempting to leave a community conversation */ +"leave_community_confirmation_alert_title" = "Leave Community"; + +/* Message in the confirmation modal when leaving a community conversation */ +"leave_community_confirmation_alert_message" = "Are you sure you want to leave %@?"; + +/* Conversation subtitle while the user in the process of leaving */ +"group_you_leaving" = "Leaving..."; + +/* Conversation subtitle if the user in the failed to leave */ +"group_leave_error" = "Failed to leave Group!"; + +/* Message within a conversation indicating the device was unable to leave a group conversation */ +"group_unable_to_leave" = "Unable to leave the Group, please try again"; + +/* Title in the confirmation modal to delete a group */ +"delete_group_confirmation_alert_title" = "Delete Group"; + +/* Message in the confirmation modal to delete a group */ +"delete_group_confirmation_alert_message" = "Are you sure you want to delete %@?"; + +/* Title in the confirmation modal when the user tries to delete a one-to-one conversation */ +"delete_conversation_confirmation_alert_title" = "Delete Conversation"; + +/* Message in the confirmation modal when the user tries to delete a one-to-one conversation */ +"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; + +/* Title in the confirmation modal when the user tries to hide the 'Note to Self' conversation */ +"hide_note_to_self_confirmation_alert_title" = "Hide Note to Self"; + +/* Message in the confirmation modal when the user tries to hide the 'Note to Self' conversation */ +"hide_note_to_self_confirmation_alert_message" = "Are you sure you want to hide %@?"; + +/* Title in the modal for updating the users profile display picture */ +"update_profile_modal_title" = "Set Display Picture"; + +/* Save action in the modal for updating the users profile display picture */ +"update_profile_modal_save" = "Save"; + +/* Remove action in the modal for updating the users profile display picture */ +"update_profile_modal_remove" = "Remove"; + +/* Title for the error when failing to remove the users profile display picture */ +"update_profile_modal_remove_error_title" = "Unable to remove avatar image"; + +/* Title for the error when the user selects a profile display picture that is too large */ +"update_profile_modal_max_size_error_title" = "Maximum File Size Exceeded"; + +/* Message for the error when the user selects a profile display picture that is too large */ +"update_profile_modal_max_size_error_message" = "Please select a smaller photo and try again"; + +/* Title for the error when the user fails to update their profile display picture */ +"update_profile_modal_error_title" = "Couldn't Update Profile"; + +/* Message for the error when the user fails to update their profile display picture */ +"update_profile_modal_error_message" = "Please check your internet connection and try again"; + +/* Placeholder when entering a nickname for a contact */ +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; + +/* The separator within a conversation indicating that following messages are unread */ +"UNREAD_MESSAGES" = "Unread Messages"; + +/* Empty state for a conversation */ +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; + +/* Empty state for a read-only conversation */ +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; + +/* Empty state for the 'Note to Self' conversation */ +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; + +/* Message to indicate a user has Community Message Requests disabled */ +"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message."; + +/* Warning to indicate one of the users devices is running an old version of Session */ +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; + +/* Ann error displayed if the device is unable to retrieve the users recovery password */ +"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue."; + +/* An error displayed when trying to send a message if the device is unable to save it to the database */ +"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages."; + +/* An error indicating that the device was unable to access the database for some reason */ +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/bin/auto-genstrings b/Session/Meta/Translations/bin/auto-genstrings deleted file mode 100755 index 0362566cd..000000000 --- a/Session/Meta/Translations/bin/auto-genstrings +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - -set -e - -BIN_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -REPO_ROOT=$BIN_DIR/../../.. -cd $REPO_ROOT - -SSK_DIR="./SignalServiceKit/src" -SAE_DIR="./SignalShareExtension" -SM_DIR="./SignalMessaging" -SCK_DIR="../SignalCoreKit" - -TARGETS="Signal/src ${SSK_DIR} ${SAE_DIR} ${SM_DIR} ${SCK_DIR}" -TMP="$(mktemp -d)" -STRINGFILE="Signal/translations/en.lproj/Localizable.strings" - -# Assert preconditions before we do any work -# We're more likely to notice errors this way - -for TARGET_DIR in $TARGETS -do - -if [ ! -d $TARGET_DIR ]; then - echo "Unable to find required directory: ${TARGET_DIR}." - exit 1 -fi - -done - -# Now that we've check all our pre-conditions, proceed with the work. - -# Search directories for .m & .h files and collect string definitions with genstrings -find $TARGETS -name "*.m" -print0 -o -name "*.h" -print0 -o -name "*.swift" -print0 | xargs -0 genstrings -o $TMP - -# We have to convert the new .strings files to UTF-8 in order to deal with them -# STRINGFILE is already UTF-8. -OLDUTF8=$(cat $STRINGFILE) -NEWUTF8=$(iconv -f UTF-16 -t UTF-8 $TMP/Localizable.strings) - -# Let's merge the old with the new .strings file: -# 1. Select old string definition lines -# 2. Setup field separators -# 3. Read old string definitions as associative array -# 4. In new file, if possible, insert old definition -# 5. Add separator and semicolon only for string definition lines -# 6. Convert output back to UTF-16 to final location -echo "$OLDUTF8" | grep -Eo '^".*"' | \ - awk 'BEGIN {FS = "[ ]*=[ ]*"; OFS = ""} \ - NR == FNR {a[$1] = $2; next} \ - {$2 = ($1 in a ? a[$1] : $2); \ - if($2 ~ /"[;]*$/){$2 = " = "$2}; \ - if($2 ~ /"$/){$2 = $2";"}; \ - print}' - <(echo "$NEWUTF8") > $STRINGFILE - diff --git a/Session/Meta/Translations/bin/pull-translations b/Session/Meta/Translations/bin/pull-translations deleted file mode 100755 index d4d2fa89d..000000000 --- a/Session/Meta/Translations/bin/pull-translations +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash - -set -x -set -e - -BIN_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -cd $BIN_DIR/.. - -# Pull all translations which are at least 80% complete -tx pull -a --minimum-perc=80 - -# Legacy hack: pull *any existing* translations regardless of their completion. -# Once supported, we don't want to drop any translations. -tx pull --force - -for dir in *.lproj -do - - # en.lproj is already utf-8 - if [[ "$dir" = "en.lproj" ]]; then - continue - fi - - pushd $dir - # Transifex pulls utf-16, but our string linting script needs utf-8. - # Plus we can see the string diffs in GH this way. - iconv -f utf-16 -t utf-8 Localizable.strings > Localizable.strings.utf8 - mv Localizable.strings.utf8 Localizable.strings - popd - -done - -# Get and build iStringsCheck from https://github.com/FredericJacobs/iStringsCheck -# This does some checks to make sure all strings are present and that interpolated strings have the right number of arguments -LINT_CMD=../../../l10n_lint/target/debug/l10n_lint - -if [ -e $LINT_CMD ] -then - $LINT_CMD en.lproj/Localizable.strings . -else - echo "Missing string linter. See: https://github.com:WhisperSystems/l10n_lint" - exit 1 -fi - -echo "Make sure you register any new localizations in XCode! (Go to Project > Signal > Localizations > Add Localizations)" diff --git a/Session/Meta/Translations/bin/push-translation-source b/Session/Meta/Translations/bin/push-translation-source deleted file mode 100755 index 51f7eddaa..000000000 --- a/Session/Meta/Translations/bin/push-translation-source +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -x -set -e - -BIN_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -cd $BIN_DIR/.. - -tx push --source - diff --git a/Session/Meta/Translations/bin/sync-translations b/Session/Meta/Translations/bin/sync-translations deleted file mode 100755 index ef3be8d28..000000000 --- a/Session/Meta/Translations/bin/sync-translations +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -set -x -set -e - -BIN_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -cd $BIN_DIR/.. - -cat < Date: Fri, 22 Sep 2023 14:49:39 +1000 Subject: [PATCH 10/12] Fixed the broken tests and updated test dependencies Properly fixed the busted migration issue Updated to the latest version of Quick and Nimble (unit testing libraries) Updated the tests based on the above --- Podfile.lock | 12 +- Session.xcodeproj/project.pbxproj | 38 +- .../Settings/ThreadSettingsViewModel.swift | 2 +- Session/Utilities/MockDataGenerator.swift | 12 +- SessionMessagingKit/Configuration.swift | 3 +- .../Migrations/_003_YDBToGRDBMigration.swift | 12 +- .../Migrations/_013_SessionUtilChanges.swift | 8 +- .../_015_BlockCommunityMessageRequests.swift | 4 +- ..._MakeBrokenProfileTimestampsNullable.swift | 78 + .../Database/Models/Profile.swift | 16 +- .../Open Groups/OpenGroupManager.swift | 6 +- .../SessionUtil+Contacts.swift | 4 +- .../Utilities/ProfileManager.swift | 4 +- .../Models/FileUploadResponseSpec.swift | 8 +- .../Contacts/BlindedIdLookupSpec.swift | 7 +- .../Jobs/Types/MessageSendJobSpec.swift | 114 +- .../Configs/ConfigContactsSpec.swift | 547 ----- .../Configs/ConfigConvoInfoVolatileSpec.swift | 267 --- .../Configs/ConfigUserGroupsSpec.swift | 589 ------ .../Configs/ConfigUserProfileSpec.swift | 414 ---- .../LibSessionUtil/LibSessionSpec.swift | 1792 ++++++++++++++++- .../LibSessionUtil/SessionUtilSpec.swift | 22 +- ...ibSessionTypeConversionUtilitiesSpec.swift | 56 +- .../Models/BatchRequestInfoSpec.swift | 29 +- .../Open Groups/Models/CapabilitiesSpec.swift | 17 +- .../Open Groups/Models/OpenGroupSpec.swift | 15 +- .../Open Groups/Models/RoomPollInfoSpec.swift | 10 +- .../Open Groups/Models/RoomSpec.swift | 8 +- .../Open Groups/Models/SOGSMessageSpec.swift | 83 +- .../Models/SendDirectMessageRequestSpec.swift | 7 +- .../Models/SendMessageRequestSpec.swift | 10 +- .../Models/UpdateMessageRequestSpec.swift | 10 +- .../Open Groups/OpenGroupAPISpec.swift | 346 ++-- .../Open Groups/OpenGroupManagerSpec.swift | 652 +++--- .../Types/NonceGeneratorSpec.swift | 8 +- .../Types/PersonalizationSpec.swift | 6 +- .../Open Groups/Types/SOGSEndpointSpec.swift | 6 +- .../Open Groups/Types/SOGSErrorSpec.swift | 6 +- .../MessageReceiverDecryptionSpec.swift | 112 +- .../MessageSenderEncryptionSpec.swift | 86 +- .../SessionThreadViewModelSpec.swift | 90 +- .../Utilities/CryptoSMKSpec.swift | 88 +- ...eadDisappearingMessagesViewModelSpec.swift | 118 +- .../ThreadSettingsViewModelSpec.swift | 177 +- .../NotificationContentViewModelSpec.swift | 71 +- .../Database/Models/IdentitySpec.swift | 29 +- .../PersistableRecordUtilitiesSpec.swift | 256 +-- .../General/ArrayUtilitiesSpec.swift | 19 +- .../General/DependenciesSpec.swift | 16 +- .../General/SessionIdSpec.swift | 23 +- .../JobRunner/JobRunnerSpec.swift | 423 ++-- .../Networking/BatchResponseSpec.swift | 104 +- .../Utilities/BencodeSpec.swift | 119 +- .../Utilities/VersionSpec.swift | 17 +- _SharedTestUtilities/Mock.swift | 12 +- _SharedTestUtilities/MockCaches.swift | 5 + .../MockUserDefaults.swift | 0 _SharedTestUtilities/Mocked.swift | 15 + _SharedTestUtilities/NimbleExtensions.swift | 2 +- _SharedTestUtilities/SynchronousStorage.swift | 10 + 60 files changed, 3579 insertions(+), 3441 deletions(-) create mode 100644 SessionMessagingKit/Database/Migrations/_016_MakeBrokenProfileTimestampsNullable.swift delete mode 100644 SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift delete mode 100644 SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift delete mode 100644 SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift delete mode 100644 SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift rename {SessionMessagingKitTests/_TestUtilities => _SharedTestUtilities}/MockUserDefaults.swift (100%) diff --git a/Podfile.lock b/Podfile.lock index 3ca93e031..3c6883d32 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -25,13 +25,13 @@ PODS: - libwebp/sharpyuv (1.3.2) - libwebp/webp (1.3.2): - libwebp/sharpyuv - - Nimble (10.0.0) + - Nimble (12.3.0) - NVActivityIndicatorView (5.1.1): - NVActivityIndicatorView/Base (= 5.1.1) - NVActivityIndicatorView/Base (5.1.1) - OpenSSL-Universal (1.1.1300) - PureLayout (3.1.9) - - Quick (5.0.1) + - Quick (7.3.0) - Reachability (3.2) - SAMKeychain (1.5.3) - SignalCoreKit (1.0.0): @@ -137,11 +137,9 @@ SPEC REPOS: - CocoaLumberjack - DifferenceKit - GRDB.swift - - Nimble - NVActivityIndicatorView - OpenSSL-Universal - PureLayout - - Quick - Reachability - SAMKeychain - SQLCipher @@ -149,6 +147,8 @@ SPEC REPOS: - WebRTC-lib trunk: - libwebp + - Nimble + - Quick - xcbeautify EXTERNAL SOURCES: @@ -190,11 +190,11 @@ SPEC CHECKSUMS: DifferenceKit: ab185c4d7f9cef8af3fcf593e5b387fb81e999ca GRDB.swift: fe420b1af49ec519c7e96e07887ee44f5dfa2b78 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 - Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84 + Nimble: f8a8219d16f176429b951e8f7e72df5c23ceddc0 NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667 OpenSSL-Universal: e7311447fd2419f57420c79524b641537387eff2 PureLayout: 5fb5e5429519627d60d079ccb1eaa7265ce7cf88 - Quick: 749aa754fd1e7d984f2000fe051e18a3a9809179 + Quick: d32871931c05547cb4e0bc9009d66a18b50d8558 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c SignalCoreKit: 1fbd8732163ef76de16cd1107d1fa3684b607e5d diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 1d057de96..d305dea61 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -575,7 +575,6 @@ FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; FD2AAAF128ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; FD2AAAF228ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; - FD2B4AFB29429D1000AB4848 /* ConfigContactsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */; }; FD2B4AFD294688D000AB4848 /* SessionUtil+Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */; }; FD2B4AFF2946C93200AB4848 /* ConfigurationSyncJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFE2946C93200AB4848 /* ConfigurationSyncJob.swift */; }; FD2B4B042949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */; }; @@ -726,7 +725,6 @@ FD8ECF7B29340FFD00C0D1BB /* SessionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */; }; FD8ECF7D2934293A00C0D1BB /* _013_SessionUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */; }; FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */; }; - FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */; }; FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */; }; FD8ECF8B2935DB4B00C0D1BB /* SharedConfigMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */; }; FD8ECF9029381FC200C0D1BB /* SessionUtil+UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */; }; @@ -747,7 +745,6 @@ FD9DD2712A72516D00ECB68E /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9DD2702A72516D00ECB68E /* TestExtensions.swift */; }; FD9DD2722A72516D00ECB68E /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9DD2702A72516D00ECB68E /* TestExtensions.swift */; }; FD9DD2732A72516D00ECB68E /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9DD2702A72516D00ECB68E /* TestExtensions.swift */; }; - FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */; }; FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */; }; FDA1E83B29A5F2D500C5C3BD /* SessionUtil+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */; }; FDA1E83D29AC71A800C5C3BD /* SessionUtilSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83C29AC71A800C5C3BD /* SessionUtilSpec.swift */; }; @@ -758,7 +755,6 @@ FDB4BBC92839BEF000B7C95D /* ProfileManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB4BBC82839BEF000B7C95D /* ProfileManagerError.swift */; }; FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */; }; FDB7400D28EBEC240094D718 /* DateHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */; }; - FDBB25E12983909300F1508E /* ConfigConvoInfoVolatileSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.swift */; }; FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */; }; FDBB25E72988BBBE00F1508E /* UIContextualAction+Theming.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */; }; FDC13D472A16E4CA007267C7 /* SubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D462A16E4CA007267C7 /* SubscribeRequest.swift */; }; @@ -921,6 +917,11 @@ FDFDE126282D05380098B17F /* MediaInteractiveDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */; }; FDFDE128282D05530098B17F /* MediaPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE127282D05530098B17F /* MediaPresentationContext.swift */; }; FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */; }; + FDFE75B12ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */; }; + FDFE75B22ABD469500655640 /* MockJobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A629DBD43D00401309 /* MockJobRunner.swift */; }; + FDFE75B32ABD469500655640 /* MockJobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A629DBD43D00401309 /* MockJobRunner.swift */; }; + FDFE75B42ABD46B600655640 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; }; + FDFE75B52ABD46B700655640 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; }; FDFF61D729F2600300F95FB0 /* Identity+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */; }; FDFF9FDF2A787F57005E0628 /* JSONEncoder+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */; }; FE5FDED6D91BB4B3FA5C104D /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A9C113D2086D3C8A68A371C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; }; @@ -1697,7 +1698,6 @@ FD29598F2A43BE5F00888A17 /* VersionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionSpec.swift; sourceTree = ""; }; FD2959912A4417A900888A17 /* PreparedSendData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreparedSendData.swift; sourceTree = ""; }; FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronousStorage.swift; sourceTree = ""; }; - FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigContactsSpec.swift; sourceTree = ""; }; FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Contacts.swift"; sourceTree = ""; }; FD2B4AFE2946C93200AB4848 /* ConfigurationSyncJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationSyncJob.swift; sourceTree = ""; }; FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueryInterfaceRequest+Utilities.swift"; sourceTree = ""; }; @@ -1846,7 +1846,6 @@ FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtil.swift; sourceTree = ""; }; FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_SessionUtilChanges.swift; sourceTree = ""; }; FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigDump.swift; sourceTree = ""; }; - FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserProfileSpec.swift; sourceTree = ""; }; FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtilError.swift; sourceTree = ""; }; FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedConfigMessage.swift; sourceTree = ""; }; FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+UserProfile.swift"; sourceTree = ""; }; @@ -1861,7 +1860,6 @@ FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = ""; }; FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSessionUtil.a; sourceTree = BUILT_PRODUCTS_DIR; }; FD9DD2702A72516D00ECB68E /* TestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestExtensions.swift; sourceTree = ""; }; - FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserGroupsSpec.swift; sourceTree = ""; }; FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionSpec.swift; sourceTree = ""; }; FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Shared.swift"; sourceTree = ""; }; FDA1E83C29AC71A800C5C3BD /* SessionUtilSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtilSpec.swift; sourceTree = ""; }; @@ -1872,7 +1870,6 @@ FDB4BBC82839BEF000B7C95D /* ProfileManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileManagerError.swift; sourceTree = ""; }; FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Utilities.swift"; sourceTree = ""; }; FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateHeaderCell.swift; sourceTree = ""; }; - FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigConvoInfoVolatileSpec.swift; sourceTree = ""; }; FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_AddJobPriority.swift; sourceTree = ""; }; FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Theming.swift"; sourceTree = ""; }; FDC13D462A16E4CA007267C7 /* SubscribeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribeRequest.swift; sourceTree = ""; }; @@ -2038,6 +2035,7 @@ FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInteractiveDismiss.swift; sourceTree = ""; }; FDFDE127282D05530098B17F /* MediaPresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPresentationContext.swift; sourceTree = ""; }; FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaZoomAnimationController.swift; sourceTree = ""; }; + FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _016_MakeBrokenProfileTimestampsNullable.swift; sourceTree = ""; }; FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identity+Utilities.swift"; sourceTree = ""; }; FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONEncoder+Utilities.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -3616,6 +3614,7 @@ FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */, FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */, FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */, + FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */, ); path = Migrations; sourceTree = ""; @@ -4044,6 +4043,7 @@ FD23CE272A67755C0000B97C /* MockCrypto.swift */, FD23CE2B2A678DF80000B97C /* MockCaches.swift */, FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */, + FD83B9D127D59495005E1583 /* MockUserDefaults.swift */, FD23CE312A67C38D0000B97C /* MockNetwork.swift */, FD96F3A629DBD43D00401309 /* MockJobRunner.swift */, FD83B9BD27CF2243005E1583 /* TestConstants.swift */, @@ -4095,7 +4095,6 @@ FD8ECF802934385900C0D1BB /* LibSessionUtil */ = { isa = PBXGroup; children = ( - FDA1E83729A5770C00C5C3BD /* Configs */, FDDC08F029A300D500BF9681 /* Utilities */, FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */, FDA1E83C29AC71A800C5C3BD /* SessionUtilSpec.swift */, @@ -4148,17 +4147,6 @@ path = Networking; sourceTree = ""; }; - FDA1E83729A5770C00C5C3BD /* Configs */ = { - isa = PBXGroup; - children = ( - FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */, - FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */, - FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.swift */, - FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */, - ); - path = Configs; - sourceTree = ""; - }; FDC13D4E2A16EE41007267C7 /* Types */ = { isa = PBXGroup; children = ( @@ -4290,7 +4278,6 @@ isa = PBXGroup; children = ( FDC438BC27BB2AB400C60D73 /* Mockable.swift */, - FD83B9D127D59495005E1583 /* MockUserDefaults.swift */, FD078E4C27E17156000769AF /* MockOGMCache.swift */, ); path = _TestUtilities; @@ -5935,6 +5922,7 @@ C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */, FD245C642850664F00B966DD /* Threading.swift in Sources */, FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */, + FDFE75B12ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift in Sources */, C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */, FD09799B27FFC82D00936362 /* Quote.swift in Sources */, FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */, @@ -6193,9 +6181,11 @@ FD23EA5F28ED00FF0058676E /* CommonMockedExtensions.swift in Sources */, FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */, FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */, + FDFE75B52ABD46B700655640 /* MockUserDefaults.swift in Sources */, FD0969FA2A6A00B000C5C365 /* Mocked.swift in Sources */, FD23EA5C28ED00F80058676E /* Mock.swift in Sources */, FD23EA6128ED0B260058676E /* CombineExtensions.swift in Sources */, + FDFE75B32ABD469500655640 /* MockJobRunner.swift in Sources */, FD9DD2712A72516D00ECB68E /* TestExtensions.swift in Sources */, FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */, ); @@ -6210,10 +6200,12 @@ FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */, FDFBB7542A2023EB00CA7350 /* BencodeSpec.swift in Sources */, FD9DD2732A72516D00ECB68E /* TestExtensions.swift in Sources */, + FDFE75B22ABD469500655640 /* MockJobRunner.swift in Sources */, FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */, FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */, FD23CE352A67C4DA0000B97C /* MockNetwork.swift in Sources */, + FDFE75B42ABD46B600655640 /* MockUserDefaults.swift in Sources */, FD23CE282A67755C0000B97C /* MockCrypto.swift in Sources */, FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */, FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */, @@ -6240,7 +6232,6 @@ FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */, FD3C906A27E417CE00CD579F /* CryptoSMKSpec.swift in Sources */, FD96F3A729DBD43D00401309 /* MockJobRunner.swift in Sources */, - FDBB25E12983909300F1508E /* ConfigConvoInfoVolatileSpec.swift in Sources */, FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */, FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */, FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */, @@ -6253,9 +6244,7 @@ FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */, FD078E4D27E17156000769AF /* MockOGMCache.swift in Sources */, FD9DD2722A72516D00ECB68E /* TestExtensions.swift in Sources */, - FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */, FDC290A627D860CE005DAE71 /* Mock.swift in Sources */, - FD2B4AFB29429D1000AB4848 /* ConfigContactsSpec.swift in Sources */, FDA1E83D29AC71A800C5C3BD /* SessionUtilSpec.swift in Sources */, FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */, FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */, @@ -6264,7 +6253,6 @@ FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */, FD23CE342A67C4D90000B97C /* MockNetwork.swift in Sources */, FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */, - FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */, FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */, FD0969F92A69FFE700C5C365 /* Mocked.swift in Sources */, diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 5246a9687..5f8edc944 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -151,7 +151,7 @@ class ThreadSettingsViewModel: SessionTableViewModel Bool { - guard let serverUrl: URL = URL(string: server.lowercased()) else { return false } + guard let serverUrl: URL = (URL(string: server.lowercased()) ?? URL(string: "http://\(server.lowercased())")) else { + return false + } let serverPort: String = OpenGroupManager.port(for: server, serverUrl: serverUrl) let serverHost: String = serverUrl.host diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift index 4e07e7aad..adc7a7ea9 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -58,14 +58,14 @@ internal extension SessionUtil { let profileNameShouldBeUpdated: Bool = ( !data.profile.name.isEmpty && profile.name != data.profile.name && - profile.lastNameUpdate < data.profile.lastNameUpdate + (profile.lastNameUpdate ?? 0) < (data.profile.lastNameUpdate ?? 0) ) let profilePictureShouldBeUpdated: Bool = ( ( profile.profilePictureUrl != data.profile.profilePictureUrl || profile.profileEncryptionKey != data.profile.profileEncryptionKey ) && - profile.lastProfilePictureUpdate < data.profile.lastProfilePictureUpdate + (profile.lastProfilePictureUpdate ?? 0) < (data.profile.lastProfilePictureUpdate ?? 0) ) if diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 7c206107d..24192128f 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -510,7 +510,7 @@ public struct ProfileManager { // Name if let name: String = name, !name.isEmpty, name != profile.name { - if sentTimestamp > profile.lastNameUpdate || (isCurrentUser && calledFromConfigHandling) { + if sentTimestamp > (profile.lastNameUpdate ?? 0) || (isCurrentUser && calledFromConfigHandling) { profileChanges.append(Profile.Columns.name.set(to: name)) profileChanges.append(Profile.Columns.lastNameUpdate.set(to: sentTimestamp)) } @@ -526,7 +526,7 @@ public struct ProfileManager { var avatarNeedsDownload: Bool = false var targetAvatarUrl: String? = nil - if sentTimestamp > profile.lastProfilePictureUpdate || (isCurrentUser && calledFromConfigHandling) { + if sentTimestamp > (profile.lastProfilePictureUpdate ?? 0) || (isCurrentUser && calledFromConfigHandling) { switch avatarUpdate { case .none: break case .uploadImageData: preconditionFailure("Invalid options for this function") diff --git a/SessionMessagingKitTests/Common Networking/Models/FileUploadResponseSpec.swift b/SessionMessagingKitTests/Common Networking/Models/FileUploadResponseSpec.swift index 499b8e658..283978f2a 100644 --- a/SessionMessagingKitTests/Common Networking/Models/FileUploadResponseSpec.swift +++ b/SessionMessagingKitTests/Common Networking/Models/FileUploadResponseSpec.swift @@ -8,11 +8,12 @@ import Nimble @testable import SessionMessagingKit class FileUploadResponseSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - a FileUploadResponse describe("a FileUploadResponse") { + // MARK: -- when decoding context("when decoding") { + // MARK: ---- handles a string id value it("handles a string id value") { let jsonData: Data = "{\"id\":\"123\"}".data(using: .utf8)! let response: FileUploadResponse? = try? JSONDecoder().decode(FileUploadResponse.self, from: jsonData) @@ -20,6 +21,7 @@ class FileUploadResponseSpec: QuickSpec { expect(response?.id).to(equal("123")) } + // MARK: ---- handles an int id value it("handles an int id value") { let jsonData: Data = "{\"id\":124}".data(using: .utf8)! let response: FileUploadResponse? = try? JSONDecoder().decode(FileUploadResponse.self, from: jsonData) diff --git a/SessionMessagingKitTests/Contacts/BlindedIdLookupSpec.swift b/SessionMessagingKitTests/Contacts/BlindedIdLookupSpec.swift index 4cb4b4cba..f70c9f424 100644 --- a/SessionMessagingKitTests/Contacts/BlindedIdLookupSpec.swift +++ b/SessionMessagingKitTests/Contacts/BlindedIdLookupSpec.swift @@ -8,11 +8,12 @@ import Nimble @testable import SessionMessagingKit class BlindedIdLookupSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - a BlindedIdLookup describe("a BlindedIdLookup") { + // MARK: -- when initializing context("when initializing") { + // MARK: ---- sets the values correctly it("sets the values correctly") { let lookup: BlindedIdLookup = BlindedIdLookup( blindedId: "testBlindedId", diff --git a/SessionMessagingKitTests/Jobs/Types/MessageSendJobSpec.swift b/SessionMessagingKitTests/Jobs/Types/MessageSendJobSpec.swift index 3de0762cf..c6af18418 100644 --- a/SessionMessagingKitTests/Jobs/Types/MessageSendJobSpec.swift +++ b/SessionMessagingKitTests/Jobs/Types/MessageSendJobSpec.swift @@ -10,49 +10,37 @@ import Nimble @testable import SessionUtilitiesKit class MessageSendJobSpec: QuickSpec { - // MARK: - Spec - - override func spec() { - var job: Job! - var interaction: Interaction! - var attachment: Attachment! - var interactionAttachment: InteractionAttachment! - var mockStorage: Storage! - var mockJobRunner: MockJobRunner! - var dependencies: Dependencies! + override class func spec() { + // MARK: Configuration - // MARK: - JobRunner - - describe("a MessageSendJob") { - // MARK: - Configuration - - beforeEach { - mockStorage = SynchronousStorage( - customWriter: try! DatabaseQueue(), - customMigrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ] + @TestState var job: Job! + @TestState var interaction: Interaction! + @TestState var attachment: Attachment! = Attachment( + id: "200", + variant: .standard, + state: .failedDownload, + contentType: "text/plain", + byteCount: 200 + ) + @TestState var interactionAttachment: InteractionAttachment! + @TestState var mockStorage: Storage! = SynchronousStorage( + customWriter: try! DatabaseQueue(), + customMigrationTargets: [ + SNUtilitiesKit.self, + SNMessagingKit.self + ], + initialData: { db in + try SessionThread.fetchOrCreate( + db, + id: "Test1", + variant: .contact, + shouldBeVisible: true ) - mockJobRunner = MockJobRunner() - dependencies = Dependencies( - storage: mockStorage, - jobRunner: mockJobRunner, - dateNow: Date(timeIntervalSince1970: 1234567890) - ) - attachment = Attachment( - id: "200", - variant: .standard, - state: .failedDownload, - contentType: "text/plain", - byteCount: 200 - ) - - mockStorage.write { db in - try SessionThread.fetchOrCreate(db, id: "Test1", variant: .contact, shouldBeVisible: true) - } - - mockJobRunner + } + ) + @TestState var mockJobRunner: MockJobRunner! = MockJobRunner( + initialSetup: { jobRunner in + jobRunner .when { $0.jobInfoFor( jobs: nil, @@ -61,7 +49,7 @@ class MessageSendJobSpec: QuickSpec { ) } .thenReturn([:]) - mockJobRunner + jobRunner .when { $0.insert(any(), job: any(), before: any()) } .then { args in let db: Database = args[0] as! Database @@ -72,14 +60,16 @@ class MessageSendJobSpec: QuickSpec { } .thenReturn((1000, Job(variant: .messageSend))) } - - afterEach { - job = nil - mockStorage = nil - dependencies = nil - } - - // MARK: - fails when not given any details + ) + @TestState var dependencies: Dependencies! = Dependencies( + storage: mockStorage, + jobRunner: mockJobRunner, + dateNow: Date(timeIntervalSince1970: 1234567890) + ) + + // MARK: - a MessageSendJob + describe("a MessageSendJob") { + // MARK: -- fails when not given any details it("fails when not given any details") { job = Job(variant: .messageSend) @@ -102,7 +92,7 @@ class MessageSendJobSpec: QuickSpec { expect(permanentFailure).to(beTrue()) } - // MARK: - fails when given incorrect details + // MARK: -- fails when given incorrect details it("fails when given incorrect details") { job = Job( variant: .messageSend, @@ -128,7 +118,7 @@ class MessageSendJobSpec: QuickSpec { expect(permanentFailure).to(beTrue()) } - // MARK: - of VisibleMessage + // MARK: -- of VisibleMessage context("of VisibleMessage") { beforeEach { interaction = Interaction( @@ -167,7 +157,7 @@ class MessageSendJobSpec: QuickSpec { } } - // MARK: -- fails when there is no job id + // MARK: ---- fails when there is no job id it("fails when there is no job id") { job = Job( variant: .messageSend, @@ -199,7 +189,7 @@ class MessageSendJobSpec: QuickSpec { expect(permanentFailure).to(beTrue()) } - // MARK: -- fails when there is no interaction id + // MARK: ---- fails when there is no interaction id it("fails when there is no interaction id") { job = Job( variant: .messageSend, @@ -230,7 +220,7 @@ class MessageSendJobSpec: QuickSpec { expect(permanentFailure).to(beTrue()) } - // MARK: -- fails when there is no interaction for the provided interaction id + // MARK: ---- fails when there is no interaction for the provided interaction id it("fails when there is no interaction for the provided interaction id") { job = Job( variant: .messageSend, @@ -263,7 +253,7 @@ class MessageSendJobSpec: QuickSpec { expect(permanentFailure).to(beTrue()) } - // MARK: -- with an attachment + // MARK: ---- with an attachment context("with an attachment") { beforeEach { interactionAttachment = InteractionAttachment( @@ -278,7 +268,7 @@ class MessageSendJobSpec: QuickSpec { } } - // MARK: ---- it fails when trying to send with an attachment which previously failed to download + // MARK: ------ it fails when trying to send with an attachment which previously failed to download it("it fails when trying to send with an attachment which previously failed to download") { mockStorage.write { db in try attachment.with(state: .failedDownload).save(db) @@ -303,7 +293,7 @@ class MessageSendJobSpec: QuickSpec { expect(permanentFailure).to(beTrue()) } - // MARK: ---- with a pending upload + // MARK: ------ with a pending upload context("with a pending upload") { beforeEach { mockStorage.write { db in @@ -311,7 +301,7 @@ class MessageSendJobSpec: QuickSpec { } } - // MARK: ------ it defers when trying to send with an attachment which is still pending upload + // MARK: -------- it defers when trying to send with an attachment which is still pending upload it("it defers when trying to send with an attachment which is still pending upload") { var didDefer: Bool = false @@ -331,7 +321,7 @@ class MessageSendJobSpec: QuickSpec { expect(didDefer).to(beTrue()) } - // MARK: ------ it defers when trying to send with an uploaded attachment that has an invalid downloadUrl + // MARK: -------- it defers when trying to send with an uploaded attachment that has an invalid downloadUrl it("it defers when trying to send with an uploaded attachment that has an invalid downloadUrl") { var didDefer: Bool = false @@ -356,7 +346,7 @@ class MessageSendJobSpec: QuickSpec { expect(didDefer).to(beTrue()) } - // MARK: ------ inserts an attachment upload job before the message send job + // MARK: -------- inserts an attachment upload job before the message send job it("inserts an attachment upload job before the message send job") { mockJobRunner .when { @@ -397,7 +387,7 @@ class MessageSendJobSpec: QuickSpec { }) } - // MARK: ------ creates a dependency between the new job and the existing one + // MARK: -------- creates a dependency between the new job and the existing one it("creates a dependency between the new job and the existing one") { MessageSendJob.run( job, diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift deleted file mode 100644 index a57839b28..000000000 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift +++ /dev/null @@ -1,547 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import GRDB -import Sodium -import SessionUtil -import SessionUtilitiesKit - -import Quick -import Nimble - -@testable import SessionMessagingKit - -/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches -class ConfigContactsSpec { - enum ContactProperty: CaseIterable { - case name - case nickname - case approved - case approved_me - case blocked - case profile_pic - case created - case notifications - case mute_until - } - - // MARK: - Spec - - static func spec() { - context("CONTACTS") { - // MARK: - when checking error catching - context("when checking error catching") { - var seed: Data! - var identity: (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair)! - var edSK: [UInt8]! - var error: UnsafeMutablePointer? - var conf: UnsafeMutablePointer? - - beforeEach { - seed = Data(hex: "0123456789abcdef0123456789abcdef") - - // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately - identity = try! Identity.generate(from: seed) - edSK = identity.ed25519KeyPair.secretKey - - // Initialize a brand new, empty config because we have no dump data to deal with. - error = nil - conf = nil - _ = contacts_init(&conf, &edSK, nil, 0, error) - error?.deallocate() - } - - // MARK: -- it can catch size limit errors thrown when pushing - it("can catch size limit errors thrown when pushing") { - var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) - - try (0..<10000).forEach { index in - var contact: contacts_contact = try createContact( - for: index, - in: conf, - rand: &randomGenerator, - maxing: .allProperties - ) - contacts_set(conf, &contact) - } - - expect(contacts_size(conf)).to(equal(10000)) - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_dump(conf)).to(beTrue()) - - expect { - try CExceptionHelper.performSafely { config_push(conf).deallocate() } - } - .to(throwError(NSError(domain: "cpp_exception", code: -2, userInfo: ["NSLocalizedDescription": "Config data is too large"]))) - } - } - - // MARK: - when checking size limits - context("when checking size limits") { - var numRecords: Int! - var seed: Data! - var identity: (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair)! - var edSK: [UInt8]! - var error: UnsafeMutablePointer? - var conf: UnsafeMutablePointer? - - beforeEach { - numRecords = 0 - seed = Data(hex: "0123456789abcdef0123456789abcdef") - - // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately - identity = try! Identity.generate(from: seed) - edSK = identity.ed25519KeyPair.secretKey - - // Initialize a brand new, empty config because we have no dump data to deal with. - error = nil - conf = nil - _ = contacts_init(&conf, &edSK, nil, 0, error) - error?.deallocate() - } - - // MARK: -- has not changed the max empty records - it("has not changed the max empty records") { - var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) - - for index in (0..<100000) { - var contact: contacts_contact = try createContact( - for: index, - in: conf, - rand: &randomGenerator - ) - contacts_set(conf, &contact) - - do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } - catch { break } - - // We successfully inserted a contact and didn't hit the limit so increment the counter - numRecords += 1 - } - - // Check that the record count matches the maximum when we last checked - expect(numRecords).to(equal(2370)) - } - - // MARK: -- has not changed the max name only records - it("has not changed the max name only records") { - var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) - - for index in (0..<100000) { - var contact: contacts_contact = try createContact( - for: index, - in: conf, - rand: &randomGenerator, - maxing: [.name] - ) - contacts_set(conf, &contact) - - do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } - catch { break } - - // We successfully inserted a contact and didn't hit the limit so increment the counter - numRecords += 1 - } - - // Check that the record count matches the maximum when we last checked - expect(numRecords).to(equal(796)) - } - - // MARK: -- has not changed the max name and profile pic only records - it("has not changed the max name and profile pic only records") { - var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) - - for index in (0..<100000) { - var contact: contacts_contact = try createContact( - for: index, - in: conf, - rand: &randomGenerator, - maxing: [.name, .profile_pic] - ) - contacts_set(conf, &contact) - - do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } - catch { break } - - // We successfully inserted a contact and didn't hit the limit so increment the counter - numRecords += 1 - } - - // Check that the record count matches the maximum when we last checked - expect(numRecords).to(equal(290)) - } - - // MARK: -- has not changed the max filled records - it("has not changed the max filled records") { - var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) - - for index in (0..<100000) { - var contact: contacts_contact = try createContact( - for: index, - in: conf, - rand: &randomGenerator, - maxing: .allProperties - ) - contacts_set(conf, &contact) - - do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } - catch { break } - - // We successfully inserted a contact and didn't hit the limit so increment the counter - numRecords += 1 - } - - // Check that the record count matches the maximum when we last checked - expect(numRecords).to(equal(236)) - } - } - - // MARK: - generates config correctly - - it("generates config correctly") { - let createdTs: Int64 = 1680064059 - let nowTs: Int64 = Int64(Date().timeIntervalSince1970) - let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") - - // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately - let identity = try! Identity.generate(from: seed) - var edSK: [UInt8] = identity.ed25519KeyPair.secretKey - expect(edSK.toHexString().suffix(64)) - .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) - expect(identity.x25519KeyPair.publicKey.toHexString()) - .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) - expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) - - // Initialize a brand new, empty config because we have no dump data to deal with. - let error: UnsafeMutablePointer? = nil - var conf: UnsafeMutablePointer? = nil - expect(contacts_init(&conf, &edSK, nil, 0, error)).to(equal(0)) - error?.deallocate() - - // Empty contacts shouldn't have an existing contact - let definitelyRealId: String = "050000000000000000000000000000000000000000000000000000000000000000" - var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() - let contactPtr: UnsafeMutablePointer? = nil - expect(contacts_get(conf, contactPtr, &cDefinitelyRealId)).to(beFalse()) - - expect(contacts_size(conf)).to(equal(0)) - - var contact2: contacts_contact = contacts_contact() - expect(contacts_get_or_construct(conf, &contact2, &cDefinitelyRealId)).to(beTrue()) - expect(String(libSessionVal: contact2.name)).to(beEmpty()) - expect(String(libSessionVal: contact2.nickname)).to(beEmpty()) - expect(contact2.approved).to(beFalse()) - expect(contact2.approved_me).to(beFalse()) - expect(contact2.blocked).to(beFalse()) - expect(contact2.profile_pic).toNot(beNil()) // Creates an empty instance apparently - expect(String(libSessionVal: contact2.profile_pic.url)).to(beEmpty()) - expect(contact2.created).to(equal(0)) - expect(contact2.notifications).to(equal(CONVO_NOTIFY_DEFAULT)) - expect(contact2.mute_until).to(equal(0)) - - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_dump(conf)).to(beFalse()) - - let pushData1: UnsafeMutablePointer = config_push(conf) - expect(pushData1.pointee.seqno).to(equal(0)) - pushData1.deallocate() - - // Update the contact data - contact2.name = "Joe".toLibSession() - contact2.nickname = "Joey".toLibSession() - contact2.approved = true - contact2.approved_me = true - contact2.created = createdTs - contact2.notifications = CONVO_NOTIFY_ALL - contact2.mute_until = nowTs + 1800 - - // Update the contact - contacts_set(conf, &contact2) - - // Ensure the contact details were updated - var contact3: contacts_contact = contacts_contact() - expect(contacts_get(conf, &contact3, &cDefinitelyRealId)).to(beTrue()) - expect(String(libSessionVal: contact3.name)).to(equal("Joe")) - expect(String(libSessionVal: contact3.nickname)).to(equal("Joey")) - expect(contact3.approved).to(beTrue()) - expect(contact3.approved_me).to(beTrue()) - expect(contact3.profile_pic).toNot(beNil()) // Creates an empty instance apparently - expect(String(libSessionVal: contact3.profile_pic.url)).to(beEmpty()) - expect(contact3.blocked).to(beFalse()) - expect(String(libSessionVal: contact3.session_id)).to(equal(definitelyRealId)) - expect(contact3.created).to(equal(createdTs)) - expect(contact2.notifications).to(equal(CONVO_NOTIFY_ALL)) - expect(contact2.mute_until).to(equal(nowTs + 1800)) - - - // Since we've made changes, we should need to push new config to the swarm, *and* should need - // to dump the updated state: - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_dump(conf)).to(beTrue()) - - // incremented since we made changes (this only increments once between - // dumps; even though we changed multiple fields here). - let pushData2: UnsafeMutablePointer = config_push(conf) - - // incremented since we made changes (this only increments once between - // dumps; even though we changed multiple fields here). - expect(pushData2.pointee.seqno).to(equal(1)) - - // Pretend we uploaded it - let fakeHash1: String = "fakehash1" - var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() - config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_dump(conf)).to(beTrue()) - pushData2.deallocate() - - // NB: Not going to check encrypted data and decryption here because that's general (not - // specific to contacts) and is covered already in the user profile tests. - var dump1: UnsafeMutablePointer? = nil - var dump1Len: Int = 0 - config_dump(conf, &dump1, &dump1Len) - - let error2: UnsafeMutablePointer? = nil - var conf2: UnsafeMutablePointer? = nil - expect(contacts_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) - error2?.deallocate() - dump1?.deallocate() - - expect(config_needs_push(conf2)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - - let pushData3: UnsafeMutablePointer = config_push(conf2) - expect(pushData3.pointee.seqno).to(equal(1)) - pushData3.deallocate() - - // Because we just called dump() above, to load up contacts2 - expect(config_needs_dump(conf)).to(beFalse()) - - // Ensure the contact details were updated - var contact4: contacts_contact = contacts_contact() - expect(contacts_get(conf2, &contact4, &cDefinitelyRealId)).to(beTrue()) - expect(String(libSessionVal: contact4.name)).to(equal("Joe")) - expect(String(libSessionVal: contact4.nickname)).to(equal("Joey")) - expect(contact4.approved).to(beTrue()) - expect(contact4.approved_me).to(beTrue()) - expect(contact4.profile_pic).toNot(beNil()) // Creates an empty instance apparently - expect(String(libSessionVal: contact4.profile_pic.url)).to(beEmpty()) - expect(contact4.blocked).to(beFalse()) - expect(contact4.created).to(equal(createdTs)) - - let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" - var cAnotherId: [CChar] = anotherId.cArray.nullTerminated() - var contact5: contacts_contact = contacts_contact() - expect(contacts_get_or_construct(conf2, &contact5, &cAnotherId)).to(beTrue()) - expect(String(libSessionVal: contact5.name)).to(beEmpty()) - expect(String(libSessionVal: contact5.nickname)).to(beEmpty()) - expect(contact5.approved).to(beFalse()) - expect(contact5.approved_me).to(beFalse()) - expect(contact5.profile_pic).toNot(beNil()) // Creates an empty instance apparently - expect(String(libSessionVal: contact5.profile_pic.url)).to(beEmpty()) - expect(contact5.blocked).to(beFalse()) - - // We're not setting any fields, but we should still keep a record of the session id - contacts_set(conf2, &contact5) - expect(config_needs_push(conf2)).to(beTrue()) - - let pushData4: UnsafeMutablePointer = config_push(conf2) - expect(pushData4.pointee.seqno).to(equal(2)) - - // Check the merging - let fakeHash2: String = "fakehash2" - var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() - var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() - var mergeData: [UnsafePointer?] = [UnsafePointer(pushData4.pointee.config)] - var mergeSize: [Int] = [pushData4.pointee.config_len] - expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) - config_confirm_pushed(conf2, pushData4.pointee.seqno, &cFakeHash2) - mergeHashes.forEach { $0?.deallocate() } - pushData4.deallocate() - - expect(config_needs_push(conf)).to(beFalse()) - - let pushData5: UnsafeMutablePointer = config_push(conf) - expect(pushData5.pointee.seqno).to(equal(2)) - pushData5.deallocate() - - // Iterate through and make sure we got everything we expected - var sessionIds: [String] = [] - var nicknames: [String] = [] - expect(contacts_size(conf)).to(equal(2)) - - var contact6: contacts_contact = contacts_contact() - let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) - while !contacts_iterator_done(contactIterator, &contact6) { - sessionIds.append(String(libSessionVal: contact6.session_id)) - nicknames.append(String(libSessionVal: contact6.nickname, nullIfEmpty: true) ?? "(N/A)") - contacts_iterator_advance(contactIterator) - } - contacts_iterator_free(contactIterator) // Need to free the iterator - - expect(sessionIds.count).to(equal(2)) - expect(sessionIds.count).to(equal(contacts_size(conf))) - expect(sessionIds.first).to(equal(definitelyRealId)) - expect(sessionIds.last).to(equal(anotherId)) - expect(nicknames.first).to(equal("Joey")) - expect(nicknames.last).to(equal("(N/A)")) - - // Conflict! Oh no! - - // On client 1 delete a contact: - contacts_erase(conf, definitelyRealId) - - // Client 2 adds a new friend: - let thirdId: String = "052222222222222222222222222222222222222222222222222222222222222222" - var cThirdId: [CChar] = thirdId.cArray.nullTerminated() - var contact7: contacts_contact = contacts_contact() - expect(contacts_get_or_construct(conf2, &contact7, &cThirdId)).to(beTrue()) - contact7.nickname = "Nickname 3".toLibSession() - contact7.approved = true - contact7.approved_me = true - contact7.profile_pic.url = "http://example.com/huge.bmp".toLibSession() - contact7.profile_pic.key = "qwerty78901234567890123456789012".data(using: .utf8)!.toLibSession() - contacts_set(conf2, &contact7) - - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_push(conf2)).to(beTrue()) - - let pushData6: UnsafeMutablePointer = config_push(conf) - expect(pushData6.pointee.seqno).to(equal(3)) - - let pushData7: UnsafeMutablePointer = config_push(conf2) - expect(pushData7.pointee.seqno).to(equal(3)) - - let pushData6Str: String = String(pointer: pushData6.pointee.config, length: pushData6.pointee.config_len, encoding: .ascii)! - let pushData7Str: String = String(pointer: pushData7.pointee.config, length: pushData7.pointee.config_len, encoding: .ascii)! - expect(pushData6Str).toNot(equal(pushData7Str)) - expect([String](pointer: pushData6.pointee.obsolete, count: pushData6.pointee.obsolete_len)) - .to(equal([fakeHash2])) - expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len)) - .to(equal([fakeHash2])) - - let fakeHash3a: String = "fakehash3a" - var cFakeHash3a: [CChar] = fakeHash3a.cArray.nullTerminated() - let fakeHash3b: String = "fakehash3b" - var cFakeHash3b: [CChar] = fakeHash3b.cArray.nullTerminated() - config_confirm_pushed(conf, pushData6.pointee.seqno, &cFakeHash3a) - config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash3b) - - var mergeHashes2: [UnsafePointer?] = [cFakeHash3b].unsafeCopy() - var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData7.pointee.config)] - var mergeSize2: [Int] = [pushData7.pointee.config_len] - expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) - expect(config_needs_push(conf)).to(beTrue()) - - var mergeHashes3: [UnsafePointer?] = [cFakeHash3a].unsafeCopy() - var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData6.pointee.config)] - var mergeSize3: [Int] = [pushData6.pointee.config_len] - expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) - expect(config_needs_push(conf2)).to(beTrue()) - mergeHashes2.forEach { $0?.deallocate() } - mergeHashes3.forEach { $0?.deallocate() } - pushData6.deallocate() - pushData7.deallocate() - - let pushData8: UnsafeMutablePointer = config_push(conf) - expect(pushData8.pointee.seqno).to(equal(4)) - - let pushData9: UnsafeMutablePointer = config_push(conf2) - expect(pushData9.pointee.seqno).to(equal(pushData8.pointee.seqno)) - - let pushData8Str: String = String(pointer: pushData8.pointee.config, length: pushData8.pointee.config_len, encoding: .ascii)! - let pushData9Str: String = String(pointer: pushData9.pointee.config, length: pushData9.pointee.config_len, encoding: .ascii)! - expect(pushData8Str).to(equal(pushData9Str)) - expect([String](pointer: pushData8.pointee.obsolete, count: pushData8.pointee.obsolete_len)) - .to(equal([fakeHash3b, fakeHash3a])) - expect([String](pointer: pushData9.pointee.obsolete, count: pushData9.pointee.obsolete_len)) - .to(equal([fakeHash3a, fakeHash3b])) - - let fakeHash4: String = "fakeHash4" - var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() - config_confirm_pushed(conf, pushData8.pointee.seqno, &cFakeHash4) - config_confirm_pushed(conf2, pushData9.pointee.seqno, &cFakeHash4) - pushData8.deallocate() - pushData9.deallocate() - - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_push(conf2)).to(beFalse()) - - // Validate the changes - var sessionIds2: [String] = [] - var nicknames2: [String] = [] - expect(contacts_size(conf)).to(equal(2)) - - var contact8: contacts_contact = contacts_contact() - let contactIterator2: UnsafeMutablePointer = contacts_iterator_new(conf) - while !contacts_iterator_done(contactIterator2, &contact8) { - sessionIds2.append(String(libSessionVal: contact8.session_id)) - nicknames2.append(String(libSessionVal: contact8.nickname, nullIfEmpty: true) ?? "(N/A)") - contacts_iterator_advance(contactIterator2) - } - contacts_iterator_free(contactIterator2) // Need to free the iterator - - expect(sessionIds2.count).to(equal(2)) - expect(sessionIds2.first).to(equal(anotherId)) - expect(sessionIds2.last).to(equal(thirdId)) - expect(nicknames2.first).to(equal("(N/A)")) - expect(nicknames2.last).to(equal("Nickname 3")) - } - } - } - - // MARK: - Convenience - - private static func createContact( - for index: Int, - in conf: UnsafeMutablePointer?, - rand: inout ARC4RandomNumberGenerator, - maxing properties: [ContactProperty] = [] - ) throws -> contacts_contact { - let postPrefixId: String = "05\(rand.nextBytes(count: 32).toHexString())" - let sessionId: String = ("05\(index)a" + postPrefixId.suffix(postPrefixId.count - "05\(index)a".count)) - var cSessionId: [CChar] = sessionId.cArray.nullTerminated() - var contact: contacts_contact = contacts_contact() - - guard contacts_get_or_construct(conf, &contact, &cSessionId) else { - throw SessionUtilError.getOrConstructFailedUnexpectedly - } - - // Set the values to the maximum data that can fit - properties.forEach { property in - switch property { - case .approved: contact.approved = true - case .approved_me: contact.approved_me = true - case .blocked: contact.blocked = true - case .created: contact.created = Int64.max - case .notifications: contact.notifications = CONVO_NOTIFY_MENTIONS_ONLY - case .mute_until: contact.mute_until = Int64.max - - case .name: - contact.name = rand.nextBytes(count: SessionUtil.libSessionMaxNameByteLength) - .toHexString() - .toLibSession() - - case .nickname: - contact.nickname = rand.nextBytes(count: SessionUtil.libSessionMaxNameByteLength) - .toHexString() - .toLibSession() - - case .profile_pic: - contact.profile_pic = user_profile_pic( - url: rand.nextBytes(count: SessionUtil.libSessionMaxProfileUrlByteLength) - .toHexString() - .toLibSession(), - key: Data(rand.nextBytes(count: 32)) - .toLibSession() - ) - } - } - - return contact - } -} - -fileprivate extension Array where Element == ConfigContactsSpec.ContactProperty { - static var allProperties: [ConfigContactsSpec.ContactProperty] = ConfigContactsSpec.ContactProperty.allCases -} diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift deleted file mode 100644 index 86325c4ad..000000000 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import Sodium -import SessionUtil -import SessionUtilitiesKit - -import Quick -import Nimble - -/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches -class ConfigConvoInfoVolatileSpec { - // MARK: - Spec - - static func spec() { - context("CONVO_INFO_VOLATILE") { - it("generates config correctly") { - let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") - - // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately - let identity = try! Identity.generate(from: seed) - var edSK: [UInt8] = identity.ed25519KeyPair.secretKey - expect(edSK.toHexString().suffix(64)) - .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) - expect(identity.x25519KeyPair.publicKey.toHexString()) - .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) - expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) - - // Initialize a brand new, empty config because we have no dump data to deal with. - let error: UnsafeMutablePointer? = nil - var conf: UnsafeMutablePointer? = nil - expect(convo_info_volatile_init(&conf, &edSK, nil, 0, error)).to(equal(0)) - error?.deallocate() - - // Empty contacts shouldn't have an existing contact - let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000" - var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() - var oneToOne1: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_1to1(conf, &oneToOne1, &cDefinitelyRealId)).to(beFalse()) - expect(convo_info_volatile_size(conf)).to(equal(0)) - - var oneToOne2: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_or_construct_1to1(conf, &oneToOne2, &cDefinitelyRealId)) - .to(beTrue()) - expect(String(libSessionVal: oneToOne2.session_id)).to(equal(definitelyRealId)) - expect(oneToOne2.last_read).to(equal(0)) - expect(oneToOne2.unread).to(beFalse()) - - // No need to sync a conversation with a default state - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_dump(conf)).to(beFalse()) - - // Update the last read - let nowTimestampMs: Int64 = Int64(floor(Date().timeIntervalSince1970 * 1000)) - oneToOne2.last_read = nowTimestampMs - - // The new data doesn't get stored until we call this: - convo_info_volatile_set_1to1(conf, &oneToOne2) - - var legacyGroup1: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - var oneToOne3: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_legacy_group(conf, &legacyGroup1, &cDefinitelyRealId)) - .to(beFalse()) - expect(convo_info_volatile_get_1to1(conf, &oneToOne3, &cDefinitelyRealId)).to(beTrue()) - expect(oneToOne3.last_read).to(equal(nowTimestampMs)) - - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_dump(conf)).to(beTrue()) - - let openGroupBaseUrl: String = "http://Example.ORG:5678" - var cOpenGroupBaseUrl: [CChar] = openGroupBaseUrl.cArray.nullTerminated() - let openGroupBaseUrlResult: String = openGroupBaseUrl.lowercased() - // ("http://Example.ORG:5678" - // .lowercased() - // .cArray + - // [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count)) - // ) - let openGroupRoom: String = "SudokuRoom" - var cOpenGroupRoom: [CChar] = openGroupRoom.cArray.nullTerminated() - let openGroupRoomResult: String = openGroupRoom.lowercased() - // ("SudokuRoom" - // .lowercased() - // .cArray + - // [CChar](repeating: 0, count: (65 - openGroupRoom.count)) - // ) - var cOpenGroupPubkey: [UInt8] = Data(hex: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") - .bytes - var community1: convo_info_volatile_community = convo_info_volatile_community() - expect(convo_info_volatile_get_or_construct_community(conf, &community1, &cOpenGroupBaseUrl, &cOpenGroupRoom, &cOpenGroupPubkey)).to(beTrue()) - expect(String(libSessionVal: community1.base_url)).to(equal(openGroupBaseUrlResult)) - expect(String(libSessionVal: community1.room)).to(equal(openGroupRoomResult)) - expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString()) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - community1.unread = true - - // The new data doesn't get stored until we call this: - convo_info_volatile_set_community(conf, &community1); - - // We don't need to push since we haven't changed anything, so this call is mainly just for - // testing: - let pushData1: UnsafeMutablePointer = config_push(conf) - expect(pushData1.pointee.seqno).to(equal(1)) - - // Pretend we uploaded it - let fakeHash1: String = "fakehash1" - var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() - config_confirm_pushed(conf, pushData1.pointee.seqno, &cFakeHash1) - expect(config_needs_dump(conf)).to(beTrue()) - expect(config_needs_push(conf)).to(beFalse()) - pushData1.deallocate() - - var dump1: UnsafeMutablePointer? = nil - var dump1Len: Int = 0 - config_dump(conf, &dump1, &dump1Len) - - let error2: UnsafeMutablePointer? = nil - var conf2: UnsafeMutablePointer? = nil - expect(convo_info_volatile_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) - error2?.deallocate() - dump1?.deallocate() - - expect(config_needs_dump(conf2)).to(beFalse()) - expect(config_needs_push(conf2)).to(beFalse()) - - var oneToOne4: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_1to1(conf2, &oneToOne4, &cDefinitelyRealId)).to(equal(true)) - expect(oneToOne4.last_read).to(equal(nowTimestampMs)) - expect(String(libSessionVal: oneToOne4.session_id)).to(equal(definitelyRealId)) - expect(oneToOne4.unread).to(beFalse()) - - var community2: convo_info_volatile_community = convo_info_volatile_community() - expect(convo_info_volatile_get_community(conf2, &community2, &cOpenGroupBaseUrl, &cOpenGroupRoom)).to(beTrue()) - expect(String(libSessionVal: community2.base_url)).to(equal(openGroupBaseUrlResult)) - expect(String(libSessionVal: community2.room)).to(equal(openGroupRoomResult)) - expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString()) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - community2.unread = true - - let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" - var cAnotherId: [CChar] = anotherId.cArray.nullTerminated() - var oneToOne5: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_or_construct_1to1(conf2, &oneToOne5, &cAnotherId)).to(beTrue()) - oneToOne5.unread = true - convo_info_volatile_set_1to1(conf2, &oneToOne5) - - let thirdId: String = "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" - var cThirdId: [CChar] = thirdId.cArray.nullTerminated() - var legacyGroup2: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - expect(convo_info_volatile_get_or_construct_legacy_group(conf2, &legacyGroup2, &cThirdId)).to(beTrue()) - legacyGroup2.last_read = (nowTimestampMs - 50) - convo_info_volatile_set_legacy_group(conf2, &legacyGroup2) - expect(config_needs_push(conf2)).to(beTrue()) - - let pushData2: UnsafeMutablePointer = config_push(conf2) - expect(pushData2.pointee.seqno).to(equal(2)) - - // Check the merging - let fakeHash2: String = "fakehash2" - var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() - var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() - var mergeData: [UnsafePointer?] = [UnsafePointer(pushData2.pointee.config)] - var mergeSize: [Int] = [pushData2.pointee.config_len] - expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) - config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash2) - pushData2.deallocate() - - expect(config_needs_push(conf)).to(beFalse()) - - for targetConf in [conf, conf2] { - // Iterate through and make sure we got everything we expected - var seen: [String] = [] - expect(convo_info_volatile_size(conf)).to(equal(4)) - expect(convo_info_volatile_size_1to1(conf)).to(equal(2)) - expect(convo_info_volatile_size_communities(conf)).to(equal(1)) - expect(convo_info_volatile_size_legacy_groups(conf)).to(equal(1)) - - var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1() - var c2: convo_info_volatile_community = convo_info_volatile_community() - var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - let it: OpaquePointer = convo_info_volatile_iterator_new(targetConf) - - while !convo_info_volatile_iterator_done(it) { - if convo_info_volatile_it_is_1to1(it, &c1) { - seen.append("1-to-1: \(String(libSessionVal: c1.session_id))") - } - else if convo_info_volatile_it_is_community(it, &c2) { - seen.append("og: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") - } - else if convo_info_volatile_it_is_legacy_group(it, &c3) { - seen.append("cl: \(String(libSessionVal: c3.group_id))") - } - - convo_info_volatile_iterator_advance(it) - } - - convo_info_volatile_iterator_free(it) - - expect(seen).to(equal([ - "1-to-1: 051111111111111111111111111111111111111111111111111111111111111111", - "1-to-1: 055000000000000000000000000000000000000000000000000000000000000000", - "og: http://example.org:5678/r/sudokuroom", - "cl: 05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" - ])) - } - - let fourthId: String = "052000000000000000000000000000000000000000000000000000000000000000" - var cFourthId: [CChar] = fourthId.cArray.nullTerminated() - expect(config_needs_push(conf)).to(beFalse()) - convo_info_volatile_erase_1to1(conf, &cFourthId) - expect(config_needs_push(conf)).to(beFalse()) - convo_info_volatile_erase_1to1(conf, &cDefinitelyRealId) - expect(config_needs_push(conf)).to(beTrue()) - expect(convo_info_volatile_size(conf)).to(equal(3)) - expect(convo_info_volatile_size_1to1(conf)).to(equal(1)) - - // Check the single-type iterators: - var seen1: [String?] = [] - var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1() - let it1: OpaquePointer = convo_info_volatile_iterator_new_1to1(conf) - - while !convo_info_volatile_iterator_done(it1) { - expect(convo_info_volatile_it_is_1to1(it1, &c1)).to(beTrue()) - - seen1.append(String(libSessionVal: c1.session_id)) - convo_info_volatile_iterator_advance(it1) - } - - convo_info_volatile_iterator_free(it1) - expect(seen1).to(equal([ - "051111111111111111111111111111111111111111111111111111111111111111" - ])) - - var seen2: [String?] = [] - var c2: convo_info_volatile_community = convo_info_volatile_community() - let it2: OpaquePointer = convo_info_volatile_iterator_new_communities(conf) - - while !convo_info_volatile_iterator_done(it2) { - expect(convo_info_volatile_it_is_community(it2, &c2)).to(beTrue()) - - seen2.append(String(libSessionVal: c2.base_url)) - convo_info_volatile_iterator_advance(it2) - } - - convo_info_volatile_iterator_free(it2) - expect(seen2).to(equal([ - "http://example.org:5678" - ])) - - var seen3: [String?] = [] - var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - let it3: OpaquePointer = convo_info_volatile_iterator_new_legacy_groups(conf) - - while !convo_info_volatile_iterator_done(it3) { - expect(convo_info_volatile_it_is_legacy_group(it3, &c3)).to(beTrue()) - - seen3.append(String(libSessionVal: c3.group_id)) - convo_info_volatile_iterator_advance(it3) - } - - convo_info_volatile_iterator_free(it3) - expect(seen3).to(equal([ - "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" - ])) - } - } - } -} diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift deleted file mode 100644 index 926cf74f6..000000000 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift +++ /dev/null @@ -1,589 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import Sodium -import SessionUtil -import SessionUtilitiesKit -import SessionMessagingKit - -import Quick -import Nimble - -/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches -class ConfigUserGroupsSpec { - // MARK: - Spec - - static func spec() { - it("parses community URLs correctly") { - let result1 = SessionUtil.parseCommunity(url: [ - "https://example.com/", - "SomeRoom?public_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - ].joined()) - let result2 = SessionUtil.parseCommunity(url: [ - "HTTPS://EXAMPLE.COM/", - "sOMErOOM?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" - ].joined()) - let result3 = SessionUtil.parseCommunity(url: [ - "HTTPS://EXAMPLE.COM/r/", - "someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" - ].joined()) - let result4 = SessionUtil.parseCommunity(url: [ - "http://example.com/r/", - "someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" - ].joined()) - let result5 = SessionUtil.parseCommunity(url: [ - "HTTPS://EXAMPLE.com:443/r/", - "someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" - ].joined()) - let result6 = SessionUtil.parseCommunity(url: [ - "HTTP://EXAMPLE.com:80/r/", - "someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" - ].joined()) - let result7 = SessionUtil.parseCommunity(url: [ - "http://example.com:80/r/", - "someroom?public_key=ASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8" - ].joined()) - let result8 = SessionUtil.parseCommunity(url: [ - "http://example.com:80/r/", - "someroom?public_key=yrtwk3hjixg66yjdeiuauk6p7hy1gtm8tgih55abrpnsxnpm3zzo" - ].joined()) - - expect(result1?.server).to(equal("https://example.com")) - expect(result1?.server).to(equal(result2?.server)) - expect(result1?.server).to(equal(result3?.server)) - expect(result1?.server).toNot(equal(result4?.server)) - expect(result4?.server).to(equal("http://example.com")) - expect(result1?.server).to(equal(result5?.server)) - expect(result4?.server).to(equal(result6?.server)) - expect(result4?.server).to(equal(result7?.server)) - expect(result4?.server).to(equal(result8?.server)) - expect(result1?.room).to(equal("SomeRoom")) - expect(result2?.room).to(equal("sOMErOOM")) - expect(result3?.room).to(equal("someroom")) - expect(result4?.room).to(equal("someroom")) - expect(result5?.room).to(equal("someroom")) - expect(result6?.room).to(equal("someroom")) - expect(result7?.room).to(equal("someroom")) - expect(result8?.room).to(equal("someroom")) - expect(result1?.publicKey) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - expect(result2?.publicKey) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - expect(result3?.publicKey) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - expect(result4?.publicKey) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - expect(result5?.publicKey) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - expect(result6?.publicKey) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - expect(result7?.publicKey) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - expect(result8?.publicKey) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - } - - context("USER_GROUPS") { - it("generates config correctly") { - let createdTs: Int64 = 1680064059 - let nowTs: Int64 = Int64(Date().timeIntervalSince1970) - let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") - - // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately - let identity = try! Identity.generate(from: seed) - var edSK: [UInt8] = identity.ed25519KeyPair.secretKey - expect(edSK.toHexString().suffix(64)) - .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) - expect(identity.x25519KeyPair.publicKey.toHexString()) - .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) - expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) - - // Initialize a brand new, empty config because we have no dump data to deal with. - let error: UnsafeMutablePointer? = nil - var conf: UnsafeMutablePointer? = nil - expect(user_groups_init(&conf, &edSK, nil, 0, error)).to(equal(0)) - error?.deallocate() - - // Empty contacts shouldn't have an existing contact - let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000" - var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() - let legacyGroup1: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cDefinitelyRealId) - expect(legacyGroup1?.pointee).to(beNil()) - expect(user_groups_size(conf)).to(equal(0)) - - let legacyGroup2: UnsafeMutablePointer = user_groups_get_or_construct_legacy_group(conf, &cDefinitelyRealId) - expect(legacyGroup2.pointee).toNot(beNil()) - expect(String(libSessionVal: legacyGroup2.pointee.session_id)) - .to(equal(definitelyRealId)) - expect(legacyGroup2.pointee.disappearing_timer).to(equal(0)) - expect(String(libSessionVal: legacyGroup2.pointee.enc_pubkey, fixedLength: 32)).to(equal("")) - expect(String(libSessionVal: legacyGroup2.pointee.enc_seckey, fixedLength: 32)).to(equal("")) - expect(legacyGroup2.pointee.priority).to(equal(0)) - expect(String(libSessionVal: legacyGroup2.pointee.name)).to(equal("")) - expect(legacyGroup2.pointee.joined_at).to(equal(0)) - expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_DEFAULT)) - expect(legacyGroup2.pointee.mute_until).to(equal(0)) - - // Iterate through and make sure we got everything we expected - var membersSeen1: [String: Bool] = [:] - var memberSessionId1: UnsafePointer? = nil - var memberAdmin1: Bool = false - let membersIt1: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2) - - while ugroups_legacy_members_next(membersIt1, &memberSessionId1, &memberAdmin1) { - membersSeen1[String(cString: memberSessionId1!)] = memberAdmin1 - } - - ugroups_legacy_members_free(membersIt1) - - expect(membersSeen1).to(beEmpty()) - - // No need to sync a conversation with a default state - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_dump(conf)).to(beFalse()) - - // We don't need to push since we haven't changed anything, so this call is mainly just for - // testing: - let pushData1: UnsafeMutablePointer = config_push(conf) - expect(pushData1.pointee.seqno).to(equal(0)) - expect([String](pointer: pushData1.pointee.obsolete, count: pushData1.pointee.obsolete_len)) - .to(beEmpty()) - expect(pushData1.pointee.config_len).to(equal(256)) - pushData1.deallocate() - - let users: [String] = [ - "050000000000000000000000000000000000000000000000000000000000000000", - "051111111111111111111111111111111111111111111111111111111111111111", - "052222222222222222222222222222222222222222222222222222222222222222", - "053333333333333333333333333333333333333333333333333333333333333333", - "054444444444444444444444444444444444444444444444444444444444444444", - "055555555555555555555555555555555555555555555555555555555555555555", - "056666666666666666666666666666666666666666666666666666666666666666" - ] - var cUsers: [[CChar]] = users.map { $0.cArray.nullTerminated() } - legacyGroup2.pointee.name = "Englishmen".toLibSession() - legacyGroup2.pointee.disappearing_timer = 60 - legacyGroup2.pointee.joined_at = createdTs - legacyGroup2.pointee.notifications = CONVO_NOTIFY_ALL - legacyGroup2.pointee.mute_until = (nowTs + 3600) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[0], false)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], true)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[4], true)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[5], false)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beFalse()) - - // Flip to and from admin - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], true)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], false)).to(beTrue()) - - expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[5])).to(beTrue()) - expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[4])).to(beTrue()) - - var membersSeen2: [String: Bool] = [:] - var memberSessionId2: UnsafePointer? = nil - var memberAdmin2: Bool = false - let membersIt2: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2) - - while ugroups_legacy_members_next(membersIt2, &memberSessionId2, &memberAdmin2) { - membersSeen2[String(cString: memberSessionId2!)] = memberAdmin2 - } - - ugroups_legacy_members_free(membersIt2) - - expect(membersSeen2).to(equal([ - "050000000000000000000000000000000000000000000000000000000000000000": false, - "051111111111111111111111111111111111111111111111111111111111111111": false, - "052222222222222222222222222222222222222222222222222222222222222222": true - ])) - - // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately - let groupSeed: Data = Data(hex: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff") - let groupEd25519KeyPair = Sodium().sign.keyPair(seed: groupSeed.bytes)! - let groupX25519PublicKey = Sodium().sign.toX25519(ed25519PublicKey: groupEd25519KeyPair.publicKey)! - - // Note: this isn't exactly what Session actually does here for legacy closed - // groups (rather it uses X25519 keys) but for this test the distinction doesn't matter. - legacyGroup2.pointee.enc_pubkey = Data(groupX25519PublicKey).toLibSession() - legacyGroup2.pointee.enc_seckey = Data(groupEd25519KeyPair.secretKey).toLibSession() - legacyGroup2.pointee.priority = 3 - - expect(Data(libSessionVal: legacyGroup2.pointee.enc_pubkey, count: 32).toHexString()) - .to(equal("c5ba413c336f2fe1fb9a2c525f8a86a412a1db128a7841b4e0e217fa9eb7fd5e")) - expect(Data(libSessionVal: legacyGroup2.pointee.enc_seckey, count: 32).toHexString()) - .to(equal("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff")) - - // The new data doesn't get stored until we call this: - user_groups_set_free_legacy_group(conf, legacyGroup2) - - let legacyGroup3: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cDefinitelyRealId) - expect(legacyGroup3?.pointee).toNot(beNil()) - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_dump(conf)).to(beTrue()) - ugroups_legacy_group_free(legacyGroup3) - - let communityPubkey: String = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - var cCommunityPubkey: [UInt8] = Data(hex: communityPubkey).cArray - var cCommunityBaseUrl: [CChar] = "http://Example.ORG:5678".cArray.nullTerminated() - var cCommunityRoom: [CChar] = "SudokuRoom".cArray.nullTerminated() - var community1: ugroups_community_info = ugroups_community_info() - expect(user_groups_get_or_construct_community(conf, &community1, &cCommunityBaseUrl, &cCommunityRoom, &cCommunityPubkey)) - .to(beTrue()) - - expect(String(libSessionVal: community1.base_url)).to(equal("http://example.org:5678")) // Note: lower-case - expect(String(libSessionVal: community1.room)).to(equal("SudokuRoom")) // Note: case-preserving - expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString()) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - community1.priority = 14 - - // The new data doesn't get stored until we call this: - user_groups_set_community(conf, &community1) - - // incremented since we made changes (this only increments once between - // dumps; even though we changed two fields here). - let pushData2: UnsafeMutablePointer = config_push(conf) - expect(pushData2.pointee.seqno).to(equal(1)) - expect([String](pointer: pushData2.pointee.obsolete, count: pushData2.pointee.obsolete_len)) - .to(beEmpty()) - - // Pretend we uploaded it - let fakeHash1: String = "fakehash1" - var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() - config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) - expect(config_needs_dump(conf)).to(beTrue()) - expect(config_needs_push(conf)).to(beFalse()) - - var dump1: UnsafeMutablePointer? = nil - var dump1Len: Int = 0 - config_dump(conf, &dump1, &dump1Len) - - let error2: UnsafeMutablePointer? = nil - var conf2: UnsafeMutablePointer? = nil - expect(user_groups_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) - error2?.deallocate() - dump1?.deallocate() - - expect(config_needs_dump(conf)).to(beFalse()) // Because we just called dump() above, to load up conf2 - expect(config_needs_push(conf)).to(beFalse()) - - let pushData3: UnsafeMutablePointer = config_push(conf) - expect(pushData3.pointee.seqno).to(equal(1)) - expect([String](pointer: pushData3.pointee.obsolete, count: pushData3.pointee.obsolete_len)) - .to(beEmpty()) - pushData3.deallocate() - - let currentHashes1: UnsafeMutablePointer? = config_current_hashes(conf) - expect([String](pointer: currentHashes1?.pointee.value, count: currentHashes1?.pointee.len)) - .to(equal(["fakehash1"])) - currentHashes1?.deallocate() - - expect(config_needs_push(conf2)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - - let pushData4: UnsafeMutablePointer = config_push(conf2) - expect(pushData4.pointee.seqno).to(equal(1)) - expect(config_needs_dump(conf2)).to(beFalse()) - expect([String](pointer: pushData4.pointee.obsolete, count: pushData4.pointee.obsolete_len)) - .to(beEmpty()) - pushData4.deallocate() - - let currentHashes2: UnsafeMutablePointer? = config_current_hashes(conf2) - expect([String](pointer: currentHashes2?.pointee.value, count: currentHashes2?.pointee.len)) - .to(equal(["fakehash1"])) - currentHashes2?.deallocate() - - expect(user_groups_size(conf2)).to(equal(2)) - expect(user_groups_size_communities(conf2)).to(equal(1)) - expect(user_groups_size_legacy_groups(conf2)).to(equal(1)) - - let legacyGroup4: UnsafeMutablePointer? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId) - expect(legacyGroup4?.pointee).toNot(beNil()) - expect(String(libSessionVal: legacyGroup4?.pointee.enc_pubkey, fixedLength: 32)).to(equal("")) - expect(String(libSessionVal: legacyGroup4?.pointee.enc_seckey, fixedLength: 32)).to(equal("")) - expect(legacyGroup4?.pointee.disappearing_timer).to(equal(60)) - expect(String(libSessionVal: legacyGroup4?.pointee.session_id)).to(equal(definitelyRealId)) - expect(legacyGroup4?.pointee.priority).to(equal(3)) - expect(String(libSessionVal: legacyGroup4?.pointee.name)).to(equal("Englishmen")) - expect(legacyGroup4?.pointee.joined_at).to(equal(createdTs)) - expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_ALL)) - expect(legacyGroup2.pointee.mute_until).to(equal(nowTs + 3600)) - - var membersSeen3: [String: Bool] = [:] - var memberSessionId3: UnsafePointer? = nil - var memberAdmin3: Bool = false - let membersIt3: OpaquePointer = ugroups_legacy_members_begin(legacyGroup4) - - while ugroups_legacy_members_next(membersIt3, &memberSessionId3, &memberAdmin3) { - membersSeen3[String(cString: memberSessionId3!)] = memberAdmin3 - } - - ugroups_legacy_members_free(membersIt3) - ugroups_legacy_group_free(legacyGroup4) - - expect(membersSeen3).to(equal([ - "050000000000000000000000000000000000000000000000000000000000000000": false, - "051111111111111111111111111111111111111111111111111111111111111111": false, - "052222222222222222222222222222222222222222222222222222222222222222": true - ])) - - expect(config_needs_push(conf2)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - - let pushData5: UnsafeMutablePointer = config_push(conf2) - expect(pushData5.pointee.seqno).to(equal(1)) - expect(config_needs_dump(conf2)).to(beFalse()) - pushData5.deallocate() - - for targetConf in [conf, conf2] { - // Iterate through and make sure we got everything we expected - var seen: [String] = [] - - var c1: ugroups_legacy_group_info = ugroups_legacy_group_info() - var c2: ugroups_community_info = ugroups_community_info() - let it: OpaquePointer = user_groups_iterator_new(targetConf) - - while !user_groups_iterator_done(it) { - if user_groups_it_is_legacy_group(it, &c1) { - var memberCount: Int = 0 - var adminCount: Int = 0 - ugroups_legacy_members_count(&c1, &memberCount, &adminCount) - seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members") - } - else if user_groups_it_is_community(it, &c2) { - seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") - } - else { - seen.append("unknown") - } - - user_groups_iterator_advance(it) - } - - user_groups_iterator_free(it) - - expect(seen).to(equal([ - "community: http://example.org:5678/r/SudokuRoom", - "legacy: Englishmen, 1 admins, 2 members" - ])) - } - - var cCommunity2BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated() - var cCommunity2Room: [CChar] = "sudokuRoom".cArray.nullTerminated() - var community2: ugroups_community_info = ugroups_community_info() - expect(user_groups_get_community(conf2, &community2, &cCommunity2BaseUrl, &cCommunity2Room)) - .to(beTrue()) - expect(String(libSessionVal: community2.base_url)).to(equal("http://example.org:5678")) - expect(String(libSessionVal: community2.room)).to(equal("SudokuRoom")) // Case preserved from the stored value, not the input value - expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString()) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - expect(community2.priority).to(equal(14)) - - expect(config_needs_push(conf2)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - - let pushData6: UnsafeMutablePointer = config_push(conf2) - expect(pushData6.pointee.seqno).to(equal(1)) - expect(config_needs_dump(conf2)).to(beFalse()) - pushData6.deallocate() - - community2.room = "sudokuRoom".toLibSession() // Change capitalization - user_groups_set_community(conf2, &community2) - - expect(config_needs_push(conf2)).to(beTrue()) - expect(config_needs_dump(conf2)).to(beTrue()) - - let fakeHash2: String = "fakehash2" - var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() - let pushData7: UnsafeMutablePointer = config_push(conf2) - expect(pushData7.pointee.seqno).to(equal(2)) - config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash2) - expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len)) - .to(equal([fakeHash1])) - - let currentHashes3: UnsafeMutablePointer? = config_current_hashes(conf2) - expect([String](pointer: currentHashes3?.pointee.value, count: currentHashes3?.pointee.len)) - .to(equal([fakeHash2])) - currentHashes3?.deallocate() - - var dump2: UnsafeMutablePointer? = nil - var dump2Len: Int = 0 - config_dump(conf2, &dump2, &dump2Len) - - expect(config_needs_push(conf2)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - - let pushData8: UnsafeMutablePointer = config_push(conf2) - expect(pushData8.pointee.seqno).to(equal(2)) - config_confirm_pushed(conf2, pushData8.pointee.seqno, &cFakeHash2) - expect(config_needs_dump(conf2)).to(beFalse()) - - var mergeHashes1: [UnsafePointer?] = [cFakeHash2].unsafeCopy() - var mergeData1: [UnsafePointer?] = [UnsafePointer(pushData8.pointee.config)] - var mergeSize1: [Int] = [pushData8.pointee.config_len] - expect(config_merge(conf, &mergeHashes1, &mergeData1, &mergeSize1, 1)).to(equal(1)) - pushData8.deallocate() - - var cCommunity3BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated() - var cCommunity3Room: [CChar] = "SudokuRoom".cArray.nullTerminated() - var community3: ugroups_community_info = ugroups_community_info() - expect(user_groups_get_community(conf, &community3, &cCommunity3BaseUrl, &cCommunity3Room)) - .to(beTrue()) - expect(String(libSessionVal: community3.room)).to(equal("sudokuRoom")) // We picked up the capitalization change - - expect(user_groups_size(conf)).to(equal(2)) - expect(user_groups_size_communities(conf)).to(equal(1)) - expect(user_groups_size_legacy_groups(conf)).to(equal(1)) - - let legacyGroup5: UnsafeMutablePointer? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId) - expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[4], false)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[5], true)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[6], true)).to(beTrue()) - expect(ugroups_legacy_member_remove(legacyGroup5, &cUsers[1])).to(beTrue()) - - expect(config_needs_push(conf2)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - - let pushData9: UnsafeMutablePointer = config_push(conf2) - expect(pushData9.pointee.seqno).to(equal(2)) - expect(config_needs_dump(conf2)).to(beFalse()) - pushData9.deallocate() - - user_groups_set_free_legacy_group(conf2, legacyGroup5) - expect(config_needs_push(conf2)).to(beTrue()) - expect(config_needs_dump(conf2)).to(beTrue()) - - var cCommunity4BaseUrl: [CChar] = "http://exAMple.ORG:5678".cArray.nullTerminated() - var cCommunity4Room: [CChar] = "sudokuROOM".cArray.nullTerminated() - user_groups_erase_community(conf2, &cCommunity4BaseUrl, &cCommunity4Room) - - let fakeHash3: String = "fakehash3" - var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated() - let pushData10: UnsafeMutablePointer = config_push(conf2) - config_confirm_pushed(conf2, pushData10.pointee.seqno, &cFakeHash3) - - expect(pushData10.pointee.seqno).to(equal(3)) - expect([String](pointer: pushData10.pointee.obsolete, count: pushData10.pointee.obsolete_len)) - .to(equal([fakeHash2])) - - let currentHashes4: UnsafeMutablePointer? = config_current_hashes(conf2) - expect([String](pointer: currentHashes4?.pointee.value, count: currentHashes4?.pointee.len)) - .to(equal([fakeHash3])) - currentHashes4?.deallocate() - - var mergeHashes2: [UnsafePointer?] = [cFakeHash3].unsafeCopy() - var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData10.pointee.config)] - var mergeSize2: [Int] = [pushData10.pointee.config_len] - expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) - - expect(user_groups_size(conf)).to(equal(1)) - expect(user_groups_size_communities(conf)).to(equal(0)) - expect(user_groups_size_legacy_groups(conf)).to(equal(1)) - - var prio: Int32 = 0 - var cBeanstalkBaseUrl: [CChar] = "http://jacksbeanstalk.org".cArray.nullTerminated() - var cBeanstalkPubkey: [UInt8] = Data( - hex: "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff" - ).cArray - - ["fee", "fi", "fo", "fum"].forEach { room in - var cRoom: [CChar] = room.cArray.nullTerminated() - prio += 1 - - var community4: ugroups_community_info = ugroups_community_info() - expect(user_groups_get_or_construct_community(conf, &community4, &cBeanstalkBaseUrl, &cRoom, &cBeanstalkPubkey)) - .to(beTrue()) - community4.priority = prio - user_groups_set_community(conf, &community4) - } - - expect(user_groups_size(conf)).to(equal(5)) - expect(user_groups_size_communities(conf)).to(equal(4)) - expect(user_groups_size_legacy_groups(conf)).to(equal(1)) - - let fakeHash4: String = "fakehash4" - var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() - let pushData11: UnsafeMutablePointer = config_push(conf) - config_confirm_pushed(conf, pushData11.pointee.seqno, &cFakeHash4) - expect(pushData11.pointee.seqno).to(equal(4)) - expect([String](pointer: pushData11.pointee.obsolete, count: pushData11.pointee.obsolete_len)) - .to(equal([fakeHash3, fakeHash2, fakeHash1])) - - // Load some obsolete ones in just to check that they get immediately obsoleted - let fakeHash10: String = "fakehash10" - let cFakeHash10: [CChar] = fakeHash10.cArray.nullTerminated() - let fakeHash11: String = "fakehash11" - let cFakeHash11: [CChar] = fakeHash11.cArray.nullTerminated() - let fakeHash12: String = "fakehash12" - let cFakeHash12: [CChar] = fakeHash12.cArray.nullTerminated() - var mergeHashes3: [UnsafePointer?] = [cFakeHash10, cFakeHash11, cFakeHash12, cFakeHash4].unsafeCopy() - var mergeData3: [UnsafePointer?] = [ - UnsafePointer(pushData10.pointee.config), - UnsafePointer(pushData2.pointee.config), - UnsafePointer(pushData7.pointee.config), - UnsafePointer(pushData11.pointee.config) - ] - var mergeSize3: [Int] = [ - pushData10.pointee.config_len, - pushData2.pointee.config_len, - pushData7.pointee.config_len, - pushData11.pointee.config_len - ] - expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 4)).to(equal(4)) - expect(config_needs_dump(conf2)).to(beTrue()) - expect(config_needs_push(conf2)).to(beFalse()) - pushData2.deallocate() - pushData7.deallocate() - pushData10.deallocate() - pushData11.deallocate() - - let currentHashes5: UnsafeMutablePointer? = config_current_hashes(conf2) - expect([String](pointer: currentHashes5?.pointee.value, count: currentHashes5?.pointee.len)) - .to(equal([fakeHash4])) - currentHashes5?.deallocate() - - let pushData12: UnsafeMutablePointer = config_push(conf2) - expect(pushData12.pointee.seqno).to(equal(4)) - expect([String](pointer: pushData12.pointee.obsolete, count: pushData12.pointee.obsolete_len)) - .to(equal([fakeHash11, fakeHash12, fakeHash10, fakeHash3])) - pushData12.deallocate() - - for targetConf in [conf, conf2] { - // Iterate through and make sure we got everything we expected - var seen: [String] = [] - - var c1: ugroups_legacy_group_info = ugroups_legacy_group_info() - var c2: ugroups_community_info = ugroups_community_info() - let it: OpaquePointer = user_groups_iterator_new(targetConf) - - while !user_groups_iterator_done(it) { - if user_groups_it_is_legacy_group(it, &c1) { - var memberCount: Int = 0 - var adminCount: Int = 0 - ugroups_legacy_members_count(&c1, &memberCount, &adminCount) - - seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members") - } - else if user_groups_it_is_community(it, &c2) { - seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") - } - else { - seen.append("unknown") - } - - user_groups_iterator_advance(it) - } - - user_groups_iterator_free(it) - - expect(seen).to(equal([ - "community: http://jacksbeanstalk.org/r/fee", - "community: http://jacksbeanstalk.org/r/fi", - "community: http://jacksbeanstalk.org/r/fo", - "community: http://jacksbeanstalk.org/r/fum", - "legacy: Englishmen, 3 admins, 2 members" - ])) - } - } - } - } -} diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift deleted file mode 100644 index 849cdc85b..000000000 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import Sodium -import SessionUtil -import SessionUtilitiesKit -import SessionMessagingKit - -import Quick -import Nimble - -/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches -class ConfigUserProfileSpec { - // MARK: - Spec - - static func spec() { - context("USER_PROFILE") { - it("generates config correctly") { - let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") - - // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately - let identity = try! Identity.generate(from: seed) - var edSK: [UInt8] = identity.ed25519KeyPair.secretKey - expect(edSK.toHexString().suffix(64)) - .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) - expect(identity.x25519KeyPair.publicKey.toHexString()) - .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) - expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) - - // Initialize a brand new, empty config because we have no dump data to deal with. - let error: UnsafeMutablePointer? = nil - var conf: UnsafeMutablePointer? = nil - expect(user_profile_init(&conf, &edSK, nil, 0, error)).to(equal(0)) - error?.deallocate() - - // We don't need to push anything, since this is an empty config - expect(config_needs_push(conf)).to(beFalse()) - // And we haven't changed anything so don't need to dump to db - expect(config_needs_dump(conf)).to(beFalse()) - - // Since it's empty there shouldn't be a name. - let namePtr: UnsafePointer? = user_profile_get_name(conf) - expect(namePtr).to(beNil()) - - // We don't need to push since we haven't changed anything, so this call is mainly just for - // testing: - let pushData1: UnsafeMutablePointer = config_push(conf) - expect(pushData1.pointee).toNot(beNil()) - expect(pushData1.pointee.seqno).to(equal(0)) - expect(pushData1.pointee.config_len).to(equal(256)) - - let encDomain: [CChar] = "UserProfile" - .bytes - .map { CChar(bitPattern: $0) } - expect(String(cString: config_encryption_domain(conf))).to(equal("UserProfile")) - - var toPushDecSize: Int = 0 - let toPushDecrypted: UnsafeMutablePointer? = config_decrypt(pushData1.pointee.config, pushData1.pointee.config_len, edSK, encDomain, &toPushDecSize) - let prefixPadding: String = (0..<193) - .map { _ in "\0" } - .joined() - expect(toPushDecrypted).toNot(beNil()) - expect(toPushDecSize).to(equal(216)) // 256 - 40 overhead - expect(String(pointer: toPushDecrypted, length: toPushDecSize)) - .to(equal("\(prefixPadding)d1:#i0e1:&de1:? = user_profile_get_name(conf) - expect(namePtr2).toNot(beNil()) - expect(String(cString: namePtr2!)).to(equal("Kallie")) - - let pic2: user_profile_pic = user_profile_get_pic(conf); - expect(String(libSessionVal: pic2.url)).to(equal("http://example.org/omg-pic-123.bmp")) - expect(Data(libSessionVal: pic2.key, count: ProfileManager.avatarAES256KeyByteLength)) - .to(equal("secret78901234567890123456789012".data(using: .utf8))) - expect(user_profile_get_nts_priority(conf)).to(equal(9)) - - // Since we've made changes, we should need to push new config to the swarm, *and* should need - // to dump the updated state: - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_dump(conf)).to(beTrue()) - - // incremented since we made changes (this only increments once between - // dumps; even though we changed two fields here). - let pushData2: UnsafeMutablePointer = config_push(conf) - expect(pushData2.pointee.seqno).to(equal(1)) - - // Note: This hex value differs from the value in the library tests because - // it looks like the library has an "end of cell mark" character added at the - // end (0x07 or '0007') so we need to manually add it to work - let expHash0: [UInt8] = Data(hex: "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965") - .bytes - // The data to be actually pushed, expanded like this to make it somewhat human-readable: - let expPush1Decrypted: [UInt8] = [""" - d - 1:#i1e - 1:& d - 1:+ i9e - 1:n 6:Kallie - 1:p 34:http://example.org/omg-pic-123.bmp - 1:q 32:secret78901234567890123456789012 - e - 1:< l - l i0e 32: - """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability - .bytes, - expHash0, - """ - de e - e - 1:= d - 1:+ 0: - 1:n 0: - 1:p 0: - 1:q 0: - e - e - """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability - .bytes - ].flatMap { $0 } - let expPush1Encrypted: [UInt8] = Data(hex: [ - "9693a69686da3055f1ecdfb239c3bf8e746951a36d888c2fb7c02e856a5c2091b24e39a7e1af828f", - "1fa09fe8bf7d274afde0a0847ba143c43ffb8722301b5ae32e2f078b9a5e19097403336e50b18c84", - "aade446cd2823b011f97d6ad2116a53feb814efecc086bc172d31f4214b4d7c630b63bbe575b0868", - "2d146da44915063a07a78556ab5eff4f67f6aa26211e8d330b53d28567a931028c393709a325425d", - "e7486ccde24416a7fd4a8ba5fa73899c65f4276dfaddd5b2100adcf0f793104fb235b31ce32ec656", - "056009a9ebf58d45d7d696b74e0c7ff0499c4d23204976f19561dc0dba6dc53a2497d28ce03498ea", - "49bf122762d7bc1d6d9c02f6d54f8384" - ].joined()).bytes - - let pushData2Str: String = String(pointer: pushData2.pointee.config, length: pushData2.pointee.config_len, encoding: .ascii)! - let expPush1EncryptedStr: String = String(pointer: expPush1Encrypted, length: expPush1Encrypted.count, encoding: .ascii)! - expect(pushData2Str).to(equal(expPush1EncryptedStr)) - - // Raw decryption doesn't unpad (i.e. the padding is part of the encrypted data) - var pushData2DecSize: Int = 0 - let pushData2Decrypted: UnsafeMutablePointer? = config_decrypt( - pushData2.pointee.config, - pushData2.pointee.config_len, - edSK, - encDomain, - &pushData2DecSize - ) - let prefixPadding2: String = (0..<(256 - 40 - expPush1Decrypted.count)) - .map { _ in "\0" } - .joined() - expect(pushData2DecSize).to(equal(216)) // 256 - 40 overhead - - let pushData2DecryptedStr: String = String(pointer: pushData2Decrypted, length: pushData2DecSize, encoding: .ascii)! - let expPush1DecryptedStr: String = String(pointer: expPush1Decrypted, length: expPush1Decrypted.count, encoding: .ascii) - .map { "\(prefixPadding2)\($0)" }! - expect(pushData2DecryptedStr).to(equal(expPush1DecryptedStr)) - pushData2Decrypted?.deallocate() - - // We haven't dumped, so still need to dump: - expect(config_needs_dump(conf)).to(beTrue()) - // We did call push, but we haven't confirmed it as stored yet, so this will still return true: - expect(config_needs_push(conf)).to(beTrue()) - - var dump1: UnsafeMutablePointer? = nil - var dump1Len: Int = 0 - - config_dump(conf, &dump1, &dump1Len) - // (in a real client we'd now store this to disk) - - expect(config_needs_dump(conf)).to(beFalse()) - - let expDump1: [CChar] = [ - """ - d - 1:! i2e - 1:$ \(expPush1Decrypted.count): - """ - .removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) - .bytes - .map { CChar(bitPattern: $0) }, - expPush1Decrypted - .map { CChar(bitPattern: $0) }, - """ - 1:(0: - 1:)le - e - """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) - .bytes - .map { CChar(bitPattern: $0) } - ].flatMap { $0 } - expect(String(pointer: dump1, length: dump1Len, encoding: .ascii)) - .to(equal(String(pointer: expDump1, length: expDump1.count, encoding: .ascii))) - dump1?.deallocate() - - // So now imagine we got back confirmation from the swarm that the push has been stored: - let fakeHash1: String = "fakehash1" - var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() - config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) - pushData2.deallocate() - - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_dump(conf)).to(beTrue()) // The confirmation changes state, so this makes us need a dump - - var dump2: UnsafeMutablePointer? = nil - var dump2Len: Int = 0 - config_dump(conf, &dump2, &dump2Len) - - let expDump2: [CChar] = [ - """ - d - 1:! i0e - 1:$ \(expPush1Decrypted.count): - """ - .removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) - .bytes - .map { CChar(bitPattern: $0) }, - expPush1Decrypted - .map { CChar(bitPattern: $0) }, - """ - 1:(9:fakehash1 - 1:)le - e - """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) - .bytes - .map { CChar(bitPattern: $0) } - ].flatMap { $0 } - expect(String(pointer: dump2, length: dump2Len, encoding: .ascii)) - .to(equal(String(pointer: expDump2, length: expDump2.count, encoding: .ascii))) - dump2?.deallocate() - expect(config_needs_dump(conf)).to(beFalse()) - - // Now we're going to set up a second, competing config object (in the real world this would be - // another Session client somewhere). - - // Start with an empty config, as above: - let error2: UnsafeMutablePointer? = nil - var conf2: UnsafeMutablePointer? = nil - expect(user_profile_init(&conf2, &edSK, nil, 0, error2)).to(equal(0)) - expect(config_needs_dump(conf2)).to(beFalse()) - error2?.deallocate() - - // Now imagine we just pulled down the `exp_push1` string from the swarm; we merge it into - // conf2: - var mergeHashes: [UnsafePointer?] = [cFakeHash1].unsafeCopy() - var mergeData: [UnsafePointer?] = [expPush1Encrypted].unsafeCopy() - var mergeSize: [Int] = [expPush1Encrypted.count] - expect(config_merge(conf2, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) - mergeHashes.forEach { $0?.deallocate() } - mergeData.forEach { $0?.deallocate() } - - // Our state has changed, so we need to dump: - expect(config_needs_dump(conf2)).to(beTrue()) - var dump3: UnsafeMutablePointer? = nil - var dump3Len: Int = 0 - config_dump(conf2, &dump3, &dump3Len) - // (store in db) - dump3?.deallocate() - expect(config_needs_dump(conf2)).to(beFalse()) - - // We *don't* need to push: even though we updated, all we did is update to the merged data (and - // didn't have any sort of merge conflict needed): - expect(config_needs_push(conf2)).to(beFalse()) - - // Now let's create a conflicting update: - - // Change the name on both clients: - user_profile_set_name(conf, "Nibbler") - user_profile_set_name(conf2, "Raz") - - // And, on conf2, we're also going to change the profile pic: - let p2: user_profile_pic = user_profile_pic( - url: "http://new.example.com/pic".toLibSession(), - key: "qwert\0yuio1234567890123456789012".data(using: .utf8)!.toLibSession() - ) - user_profile_set_pic(conf2, p2) - - user_profile_set_nts_expiry(conf2, 86400) - expect(user_profile_get_nts_expiry(conf2)).to(equal(86400)) - - expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(-1)) - user_profile_set_blinded_msgreqs(conf2, 0) - expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(0)) - user_profile_set_blinded_msgreqs(conf2, -1) - expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(-1)) - user_profile_set_blinded_msgreqs(conf2, 1) - expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(1)) - - // Both have changes, so push need a push - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_push(conf2)).to(beTrue()) - - let fakeHash2: String = "fakehash2" - var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() - let pushData3: UnsafeMutablePointer = config_push(conf) - expect(pushData3.pointee.seqno).to(equal(2)) // incremented, since we made a field change - config_confirm_pushed(conf, pushData3.pointee.seqno, &cFakeHash2) - - let fakeHash3: String = "fakehash3" - var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated() - let pushData4: UnsafeMutablePointer = config_push(conf2) - expect(pushData4.pointee.seqno).to(equal(2)) // incremented, since we made a field change - config_confirm_pushed(conf, pushData4.pointee.seqno, &cFakeHash3) - - var dump4: UnsafeMutablePointer? = nil - var dump4Len: Int = 0 - config_dump(conf, &dump4, &dump4Len); - var dump5: UnsafeMutablePointer? = nil - var dump5Len: Int = 0 - config_dump(conf2, &dump5, &dump5Len); - // (store in db) - dump4?.deallocate() - dump5?.deallocate() - - // Since we set different things, we're going to get back different serialized data to be - // pushed: - let pushData3Str: String? = String(pointer: pushData3.pointee.config, length: pushData3.pointee.config_len, encoding: .ascii) - let pushData4Str: String? = String(pointer: pushData4.pointee.config, length: pushData4.pointee.config_len, encoding: .ascii) - expect(pushData3Str).toNot(equal(pushData4Str)) - - // Now imagine that each client pushed its `seqno=2` config to the swarm, but then each client - // also fetches new messages and pulls down the other client's `seqno=2` value. - - // Feed the new config into each other. (This array could hold multiple configs if we pulled - // down more than one). - var mergeHashes2: [UnsafePointer?] = [cFakeHash2].unsafeCopy() - var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData3.pointee.config)] - var mergeSize2: [Int] = [pushData3.pointee.config_len] - expect(config_merge(conf2, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) - pushData3.deallocate() - var mergeHashes3: [UnsafePointer?] = [cFakeHash3].unsafeCopy() - var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData4.pointee.config)] - var mergeSize3: [Int] = [pushData4.pointee.config_len] - expect(config_merge(conf, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) - pushData4.deallocate() - - // Now after the merge we *will* want to push from both client, since both will have generated a - // merge conflict update (with seqno = 3). - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_push(conf2)).to(beTrue()) - let pushData5: UnsafeMutablePointer = config_push(conf) - let pushData6: UnsafeMutablePointer = config_push(conf2) - expect(pushData5.pointee.seqno).to(equal(3)) - expect(pushData6.pointee.seqno).to(equal(3)) - - // They should have resolved the conflict to the same thing: - expect(String(cString: user_profile_get_name(conf)!)).to(equal("Nibbler")) - expect(String(cString: user_profile_get_name(conf2)!)).to(equal("Nibbler")) - // (Note that they could have also both resolved to "Raz" here, but the hash of the serialized - // message just happens to have a higher hash -- and thus gets priority -- for this particular - // test). - - // Since only one of them set a profile pic there should be no conflict there: - let pic3: user_profile_pic = user_profile_get_pic(conf) - expect(pic3.url).toNot(beNil()) - expect(String(libSessionVal: pic3.url)).to(equal("http://new.example.com/pic")) - expect(pic3.key).toNot(beNil()) - expect(Data(libSessionVal: pic3.key, count: 32).toHexString()) - .to(equal("7177657274007975696f31323334353637383930313233343536373839303132")) - let pic4: user_profile_pic = user_profile_get_pic(conf2) - expect(pic4.url).toNot(beNil()) - expect(String(libSessionVal: pic4.url)).to(equal("http://new.example.com/pic")) - expect(pic4.key).toNot(beNil()) - expect(Data(libSessionVal: pic4.key, count: 32).toHexString()) - .to(equal("7177657274007975696f31323334353637383930313233343536373839303132")) - expect(user_profile_get_nts_priority(conf)).to(equal(9)) - expect(user_profile_get_nts_priority(conf2)).to(equal(9)) - expect(user_profile_get_nts_expiry(conf)).to(equal(86400)) - expect(user_profile_get_nts_expiry(conf2)).to(equal(86400)) - expect(user_profile_get_blinded_msgreqs(conf)).to(equal(1)) - expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(1)) - - let fakeHash4: String = "fakehash4" - var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() - let fakeHash5: String = "fakehash5" - var cFakeHash5: [CChar] = fakeHash5.cArray.nullTerminated() - config_confirm_pushed(conf, pushData5.pointee.seqno, &cFakeHash4) - config_confirm_pushed(conf2, pushData6.pointee.seqno, &cFakeHash5) - pushData5.deallocate() - pushData6.deallocate() - - var dump6: UnsafeMutablePointer? = nil - var dump6Len: Int = 0 - config_dump(conf, &dump6, &dump6Len); - var dump7: UnsafeMutablePointer? = nil - var dump7Len: Int = 0 - config_dump(conf2, &dump7, &dump7Len); - // (store in db) - dump6?.deallocate() - dump7?.deallocate() - - expect(config_needs_dump(conf)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_push(conf2)).to(beFalse()) - - // Wouldn't do this in a normal session but doing it here to properly clean up - // after the test - conf?.deallocate() - conf2?.deallocate() - } - } - } -} diff --git a/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift b/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift index 42d0746d7..af6726f5d 100644 --- a/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift @@ -1,6 +1,7 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation +import GRDB import Sodium import SessionUtil import SessionUtilitiesKit @@ -8,15 +9,1792 @@ import SessionUtilitiesKit import Quick import Nimble -class LibSessionSpec: QuickSpec { - // MARK: - Spec +@testable import SessionMessagingKit - override func spec() { +class LibSessionSpec: QuickSpec { + override class func spec() { + // MARK: - libSession describe("libSession") { - ConfigContactsSpec.spec() - ConfigUserProfileSpec.spec() - ConfigConvoInfoVolatileSpec.spec() - ConfigUserGroupsSpec.spec() + contactsSpec() + userProfileSpec() + convoInfoVolatileSpec() + userGroupsSpec() + + // MARK: -- parses community URLs correctly + it("parses community URLs correctly") { + let result1 = SessionUtil.parseCommunity(url: [ + "https://example.com/", + "SomeRoom?public_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + ].joined()) + let result2 = SessionUtil.parseCommunity(url: [ + "HTTPS://EXAMPLE.COM/", + "sOMErOOM?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" + ].joined()) + let result3 = SessionUtil.parseCommunity(url: [ + "HTTPS://EXAMPLE.COM/r/", + "someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" + ].joined()) + let result4 = SessionUtil.parseCommunity(url: [ + "http://example.com/r/", + "someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" + ].joined()) + let result5 = SessionUtil.parseCommunity(url: [ + "HTTPS://EXAMPLE.com:443/r/", + "someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" + ].joined()) + let result6 = SessionUtil.parseCommunity(url: [ + "HTTP://EXAMPLE.com:80/r/", + "someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" + ].joined()) + let result7 = SessionUtil.parseCommunity(url: [ + "http://example.com:80/r/", + "someroom?public_key=ASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8" + ].joined()) + let result8 = SessionUtil.parseCommunity(url: [ + "http://example.com:80/r/", + "someroom?public_key=yrtwk3hjixg66yjdeiuauk6p7hy1gtm8tgih55abrpnsxnpm3zzo" + ].joined()) + + expect(result1?.server).to(equal("https://example.com")) + expect(result1?.server).to(equal(result2?.server)) + expect(result1?.server).to(equal(result3?.server)) + expect(result1?.server).toNot(equal(result4?.server)) + expect(result4?.server).to(equal("http://example.com")) + expect(result1?.server).to(equal(result5?.server)) + expect(result4?.server).to(equal(result6?.server)) + expect(result4?.server).to(equal(result7?.server)) + expect(result4?.server).to(equal(result8?.server)) + expect(result1?.room).to(equal("SomeRoom")) + expect(result2?.room).to(equal("sOMErOOM")) + expect(result3?.room).to(equal("someroom")) + expect(result4?.room).to(equal("someroom")) + expect(result5?.room).to(equal("someroom")) + expect(result6?.room).to(equal("someroom")) + expect(result7?.room).to(equal("someroom")) + expect(result8?.room).to(equal("someroom")) + expect(result1?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result2?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result3?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result4?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result5?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result6?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result7?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result8?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + } + } + } +} + +// MARK: - CONTACTS + +fileprivate extension LibSessionSpec { + enum ContactProperty: CaseIterable { + case name + case nickname + case approved + case approved_me + case blocked + case profile_pic + case created + case notifications + case mute_until + } + + class func contactsSpec() { + context("CONTACTS") { + // MARK: -- when checking error catching + context("when checking error catching") { + var seed: Data! + var identity: (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair)! + var edSK: [UInt8]! + var error: UnsafeMutablePointer? + var conf: UnsafeMutablePointer? + + beforeEach { + seed = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + identity = try! Identity.generate(from: seed) + edSK = identity.ed25519KeyPair.secretKey + + // Initialize a brand new, empty config because we have no dump data to deal with. + error = nil + conf = nil + _ = contacts_init(&conf, &edSK, nil, 0, error) + error?.deallocate() + } + + // MARK: ---- it can catch size limit errors thrown when pushing + it("can catch size limit errors thrown when pushing") { + var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) + + try (0..<10000).forEach { index in + var contact: contacts_contact = try createContact( + for: index, + in: conf, + rand: &randomGenerator, + maxing: .allProperties + ) + contacts_set(conf, &contact) + } + + expect(contacts_size(conf)).to(equal(10000)) + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + + expect { + try CExceptionHelper.performSafely { config_push(conf).deallocate() } + } + .to(throwError(NSError(domain: "cpp_exception", code: -2, userInfo: ["NSLocalizedDescription": "Config data is too large"]))) + } + } + + // MARK: -- when checking size limits + context("when checking size limits") { + var numRecords: Int! + var seed: Data! + var identity: (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair)! + var edSK: [UInt8]! + var error: UnsafeMutablePointer? + var conf: UnsafeMutablePointer? + + beforeEach { + numRecords = 0 + seed = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + identity = try! Identity.generate(from: seed) + edSK = identity.ed25519KeyPair.secretKey + + // Initialize a brand new, empty config because we have no dump data to deal with. + error = nil + conf = nil + _ = contacts_init(&conf, &edSK, nil, 0, error) + error?.deallocate() + } + + // MARK: ---- has not changed the max empty records + it("has not changed the max empty records") { + var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) + + for index in (0..<100000) { + var contact: contacts_contact = try createContact( + for: index, + in: conf, + rand: &randomGenerator + ) + contacts_set(conf, &contact) + + do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } + catch { break } + + // We successfully inserted a contact and didn't hit the limit so increment the counter + numRecords += 1 + } + + // Check that the record count matches the maximum when we last checked + expect(numRecords).to(equal(2370)) + } + + // MARK: ---- has not changed the max name only records + it("has not changed the max name only records") { + var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) + + for index in (0..<100000) { + var contact: contacts_contact = try createContact( + for: index, + in: conf, + rand: &randomGenerator, + maxing: [.name] + ) + contacts_set(conf, &contact) + + do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } + catch { break } + + // We successfully inserted a contact and didn't hit the limit so increment the counter + numRecords += 1 + } + + // Check that the record count matches the maximum when we last checked + expect(numRecords).to(equal(796)) + } + + // MARK: ---- has not changed the max name and profile pic only records + it("has not changed the max name and profile pic only records") { + var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) + + for index in (0..<100000) { + var contact: contacts_contact = try createContact( + for: index, + in: conf, + rand: &randomGenerator, + maxing: [.name, .profile_pic] + ) + contacts_set(conf, &contact) + + do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } + catch { break } + + // We successfully inserted a contact and didn't hit the limit so increment the counter + numRecords += 1 + } + + // Check that the record count matches the maximum when we last checked + expect(numRecords).to(equal(290)) + } + + // MARK: ---- has not changed the max filled records + it("has not changed the max filled records") { + var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) + + for index in (0..<100000) { + var contact: contacts_contact = try createContact( + for: index, + in: conf, + rand: &randomGenerator, + maxing: .allProperties + ) + contacts_set(conf, &contact) + + do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } + catch { break } + + // We successfully inserted a contact and didn't hit the limit so increment the counter + numRecords += 1 + } + + // Check that the record count matches the maximum when we last checked + expect(numRecords).to(equal(236)) + } + } + + // MARK: -- generates config correctly + + it("generates config correctly") { + let createdTs: Int64 = 1680064059 + let nowTs: Int64 = Int64(Date().timeIntervalSince1970) + let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let identity = try! Identity.generate(from: seed) + var edSK: [UInt8] = identity.ed25519KeyPair.secretKey + expect(edSK.toHexString().suffix(64)) + .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) + expect(identity.x25519KeyPair.publicKey.toHexString()) + .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) + expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) + + // Initialize a brand new, empty config because we have no dump data to deal with. + let error: UnsafeMutablePointer? = nil + var conf: UnsafeMutablePointer? = nil + expect(contacts_init(&conf, &edSK, nil, 0, error)).to(equal(0)) + error?.deallocate() + + // Empty contacts shouldn't have an existing contact + let definitelyRealId: String = "050000000000000000000000000000000000000000000000000000000000000000" + var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() + let contactPtr: UnsafeMutablePointer? = nil + expect(contacts_get(conf, contactPtr, &cDefinitelyRealId)).to(beFalse()) + + expect(contacts_size(conf)).to(equal(0)) + + var contact2: contacts_contact = contacts_contact() + expect(contacts_get_or_construct(conf, &contact2, &cDefinitelyRealId)).to(beTrue()) + expect(String(libSessionVal: contact2.name)).to(beEmpty()) + expect(String(libSessionVal: contact2.nickname)).to(beEmpty()) + expect(contact2.approved).to(beFalse()) + expect(contact2.approved_me).to(beFalse()) + expect(contact2.blocked).to(beFalse()) + expect(contact2.profile_pic).toNot(beNil()) // Creates an empty instance apparently + expect(String(libSessionVal: contact2.profile_pic.url)).to(beEmpty()) + expect(contact2.created).to(equal(0)) + expect(contact2.notifications).to(equal(CONVO_NOTIFY_DEFAULT)) + expect(contact2.mute_until).to(equal(0)) + + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beFalse()) + + let pushData1: UnsafeMutablePointer = config_push(conf) + expect(pushData1.pointee.seqno).to(equal(0)) + pushData1.deallocate() + + // Update the contact data + contact2.name = "Joe".toLibSession() + contact2.nickname = "Joey".toLibSession() + contact2.approved = true + contact2.approved_me = true + contact2.created = createdTs + contact2.notifications = CONVO_NOTIFY_ALL + contact2.mute_until = nowTs + 1800 + + // Update the contact + contacts_set(conf, &contact2) + + // Ensure the contact details were updated + var contact3: contacts_contact = contacts_contact() + expect(contacts_get(conf, &contact3, &cDefinitelyRealId)).to(beTrue()) + expect(String(libSessionVal: contact3.name)).to(equal("Joe")) + expect(String(libSessionVal: contact3.nickname)).to(equal("Joey")) + expect(contact3.approved).to(beTrue()) + expect(contact3.approved_me).to(beTrue()) + expect(contact3.profile_pic).toNot(beNil()) // Creates an empty instance apparently + expect(String(libSessionVal: contact3.profile_pic.url)).to(beEmpty()) + expect(contact3.blocked).to(beFalse()) + expect(String(libSessionVal: contact3.session_id)).to(equal(definitelyRealId)) + expect(contact3.created).to(equal(createdTs)) + expect(contact2.notifications).to(equal(CONVO_NOTIFY_ALL)) + expect(contact2.mute_until).to(equal(nowTs + 1800)) + + + // Since we've made changes, we should need to push new config to the swarm, *and* should need + // to dump the updated state: + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + + // incremented since we made changes (this only increments once between + // dumps; even though we changed multiple fields here). + let pushData2: UnsafeMutablePointer = config_push(conf) + + // incremented since we made changes (this only increments once between + // dumps; even though we changed multiple fields here). + expect(pushData2.pointee.seqno).to(equal(1)) + + // Pretend we uploaded it + let fakeHash1: String = "fakehash1" + var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() + config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beTrue()) + pushData2.deallocate() + + // NB: Not going to check encrypted data and decryption here because that's general (not + // specific to contacts) and is covered already in the user profile tests. + var dump1: UnsafeMutablePointer? = nil + var dump1Len: Int = 0 + config_dump(conf, &dump1, &dump1Len) + + let error2: UnsafeMutablePointer? = nil + var conf2: UnsafeMutablePointer? = nil + expect(contacts_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) + error2?.deallocate() + dump1?.deallocate() + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData3: UnsafeMutablePointer = config_push(conf2) + expect(pushData3.pointee.seqno).to(equal(1)) + pushData3.deallocate() + + // Because we just called dump() above, to load up contacts2 + expect(config_needs_dump(conf)).to(beFalse()) + + // Ensure the contact details were updated + var contact4: contacts_contact = contacts_contact() + expect(contacts_get(conf2, &contact4, &cDefinitelyRealId)).to(beTrue()) + expect(String(libSessionVal: contact4.name)).to(equal("Joe")) + expect(String(libSessionVal: contact4.nickname)).to(equal("Joey")) + expect(contact4.approved).to(beTrue()) + expect(contact4.approved_me).to(beTrue()) + expect(contact4.profile_pic).toNot(beNil()) // Creates an empty instance apparently + expect(String(libSessionVal: contact4.profile_pic.url)).to(beEmpty()) + expect(contact4.blocked).to(beFalse()) + expect(contact4.created).to(equal(createdTs)) + + let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" + var cAnotherId: [CChar] = anotherId.cArray.nullTerminated() + var contact5: contacts_contact = contacts_contact() + expect(contacts_get_or_construct(conf2, &contact5, &cAnotherId)).to(beTrue()) + expect(String(libSessionVal: contact5.name)).to(beEmpty()) + expect(String(libSessionVal: contact5.nickname)).to(beEmpty()) + expect(contact5.approved).to(beFalse()) + expect(contact5.approved_me).to(beFalse()) + expect(contact5.profile_pic).toNot(beNil()) // Creates an empty instance apparently + expect(String(libSessionVal: contact5.profile_pic.url)).to(beEmpty()) + expect(contact5.blocked).to(beFalse()) + + // We're not setting any fields, but we should still keep a record of the session id + contacts_set(conf2, &contact5) + expect(config_needs_push(conf2)).to(beTrue()) + + let pushData4: UnsafeMutablePointer = config_push(conf2) + expect(pushData4.pointee.seqno).to(equal(2)) + + // Check the merging + let fakeHash2: String = "fakehash2" + var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() + var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() + var mergeData: [UnsafePointer?] = [UnsafePointer(pushData4.pointee.config)] + var mergeSize: [Int] = [pushData4.pointee.config_len] + expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) + config_confirm_pushed(conf2, pushData4.pointee.seqno, &cFakeHash2) + mergeHashes.forEach { $0?.deallocate() } + pushData4.deallocate() + + expect(config_needs_push(conf)).to(beFalse()) + + let pushData5: UnsafeMutablePointer = config_push(conf) + expect(pushData5.pointee.seqno).to(equal(2)) + pushData5.deallocate() + + // Iterate through and make sure we got everything we expected + var sessionIds: [String] = [] + var nicknames: [String] = [] + expect(contacts_size(conf)).to(equal(2)) + + var contact6: contacts_contact = contacts_contact() + let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) + while !contacts_iterator_done(contactIterator, &contact6) { + sessionIds.append(String(libSessionVal: contact6.session_id)) + nicknames.append(String(libSessionVal: contact6.nickname, nullIfEmpty: true) ?? "(N/A)") + contacts_iterator_advance(contactIterator) + } + contacts_iterator_free(contactIterator) // Need to free the iterator + + expect(sessionIds.count).to(equal(2)) + expect(sessionIds.count).to(equal(contacts_size(conf))) + expect(sessionIds.first).to(equal(definitelyRealId)) + expect(sessionIds.last).to(equal(anotherId)) + expect(nicknames.first).to(equal("Joey")) + expect(nicknames.last).to(equal("(N/A)")) + + // Conflict! Oh no! + + // On client 1 delete a contact: + contacts_erase(conf, definitelyRealId) + + // Client 2 adds a new friend: + let thirdId: String = "052222222222222222222222222222222222222222222222222222222222222222" + var cThirdId: [CChar] = thirdId.cArray.nullTerminated() + var contact7: contacts_contact = contacts_contact() + expect(contacts_get_or_construct(conf2, &contact7, &cThirdId)).to(beTrue()) + contact7.nickname = "Nickname 3".toLibSession() + contact7.approved = true + contact7.approved_me = true + contact7.profile_pic.url = "http://example.com/huge.bmp".toLibSession() + contact7.profile_pic.key = "qwerty78901234567890123456789012".data(using: .utf8)!.toLibSession() + contacts_set(conf2, &contact7) + + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_push(conf2)).to(beTrue()) + + let pushData6: UnsafeMutablePointer = config_push(conf) + expect(pushData6.pointee.seqno).to(equal(3)) + + let pushData7: UnsafeMutablePointer = config_push(conf2) + expect(pushData7.pointee.seqno).to(equal(3)) + + let pushData6Str: String = String(pointer: pushData6.pointee.config, length: pushData6.pointee.config_len, encoding: .ascii)! + let pushData7Str: String = String(pointer: pushData7.pointee.config, length: pushData7.pointee.config_len, encoding: .ascii)! + expect(pushData6Str).toNot(equal(pushData7Str)) + expect([String](pointer: pushData6.pointee.obsolete, count: pushData6.pointee.obsolete_len)) + .to(equal([fakeHash2])) + expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len)) + .to(equal([fakeHash2])) + + let fakeHash3a: String = "fakehash3a" + var cFakeHash3a: [CChar] = fakeHash3a.cArray.nullTerminated() + let fakeHash3b: String = "fakehash3b" + var cFakeHash3b: [CChar] = fakeHash3b.cArray.nullTerminated() + config_confirm_pushed(conf, pushData6.pointee.seqno, &cFakeHash3a) + config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash3b) + + var mergeHashes2: [UnsafePointer?] = [cFakeHash3b].unsafeCopy() + var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData7.pointee.config)] + var mergeSize2: [Int] = [pushData7.pointee.config_len] + expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) + expect(config_needs_push(conf)).to(beTrue()) + + var mergeHashes3: [UnsafePointer?] = [cFakeHash3a].unsafeCopy() + var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData6.pointee.config)] + var mergeSize3: [Int] = [pushData6.pointee.config_len] + expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) + expect(config_needs_push(conf2)).to(beTrue()) + mergeHashes2.forEach { $0?.deallocate() } + mergeHashes3.forEach { $0?.deallocate() } + pushData6.deallocate() + pushData7.deallocate() + + let pushData8: UnsafeMutablePointer = config_push(conf) + expect(pushData8.pointee.seqno).to(equal(4)) + + let pushData9: UnsafeMutablePointer = config_push(conf2) + expect(pushData9.pointee.seqno).to(equal(pushData8.pointee.seqno)) + + let pushData8Str: String = String(pointer: pushData8.pointee.config, length: pushData8.pointee.config_len, encoding: .ascii)! + let pushData9Str: String = String(pointer: pushData9.pointee.config, length: pushData9.pointee.config_len, encoding: .ascii)! + expect(pushData8Str).to(equal(pushData9Str)) + expect([String](pointer: pushData8.pointee.obsolete, count: pushData8.pointee.obsolete_len)) + .to(equal([fakeHash3b, fakeHash3a])) + expect([String](pointer: pushData9.pointee.obsolete, count: pushData9.pointee.obsolete_len)) + .to(equal([fakeHash3a, fakeHash3b])) + + let fakeHash4: String = "fakeHash4" + var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() + config_confirm_pushed(conf, pushData8.pointee.seqno, &cFakeHash4) + config_confirm_pushed(conf2, pushData9.pointee.seqno, &cFakeHash4) + pushData8.deallocate() + pushData9.deallocate() + + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_push(conf2)).to(beFalse()) + + // Validate the changes + var sessionIds2: [String] = [] + var nicknames2: [String] = [] + expect(contacts_size(conf)).to(equal(2)) + + var contact8: contacts_contact = contacts_contact() + let contactIterator2: UnsafeMutablePointer = contacts_iterator_new(conf) + while !contacts_iterator_done(contactIterator2, &contact8) { + sessionIds2.append(String(libSessionVal: contact8.session_id)) + nicknames2.append(String(libSessionVal: contact8.nickname, nullIfEmpty: true) ?? "(N/A)") + contacts_iterator_advance(contactIterator2) + } + contacts_iterator_free(contactIterator2) // Need to free the iterator + + expect(sessionIds2.count).to(equal(2)) + expect(sessionIds2.first).to(equal(anotherId)) + expect(sessionIds2.last).to(equal(thirdId)) + expect(nicknames2.first).to(equal("(N/A)")) + expect(nicknames2.last).to(equal("Nickname 3")) + } + } + } + + // MARK: - Convenience + + private static func createContact( + for index: Int, + in conf: UnsafeMutablePointer?, + rand: inout ARC4RandomNumberGenerator, + maxing properties: [ContactProperty] = [] + ) throws -> contacts_contact { + let postPrefixId: String = "05\(rand.nextBytes(count: 32).toHexString())" + let sessionId: String = ("05\(index)a" + postPrefixId.suffix(postPrefixId.count - "05\(index)a".count)) + var cSessionId: [CChar] = sessionId.cArray.nullTerminated() + var contact: contacts_contact = contacts_contact() + + guard contacts_get_or_construct(conf, &contact, &cSessionId) else { + throw SessionUtilError.getOrConstructFailedUnexpectedly + } + + // Set the values to the maximum data that can fit + properties.forEach { property in + switch property { + case .approved: contact.approved = true + case .approved_me: contact.approved_me = true + case .blocked: contact.blocked = true + case .created: contact.created = Int64.max + case .notifications: contact.notifications = CONVO_NOTIFY_MENTIONS_ONLY + case .mute_until: contact.mute_until = Int64.max + + case .name: + contact.name = rand.nextBytes(count: SessionUtil.libSessionMaxNameByteLength) + .toHexString() + .toLibSession() + + case .nickname: + contact.nickname = rand.nextBytes(count: SessionUtil.libSessionMaxNameByteLength) + .toHexString() + .toLibSession() + + case .profile_pic: + contact.profile_pic = user_profile_pic( + url: rand.nextBytes(count: SessionUtil.libSessionMaxProfileUrlByteLength) + .toHexString() + .toLibSession(), + key: Data(rand.nextBytes(count: 32)) + .toLibSession() + ) + } + } + + return contact + } +} + +fileprivate extension Array where Element == LibSessionSpec.ContactProperty { + static var allProperties: [LibSessionSpec.ContactProperty] = LibSessionSpec.ContactProperty.allCases +} + +// MARK: - USER_PROFILE + +fileprivate extension LibSessionSpec { + class func userProfileSpec() { + context("USER_PROFILE") { + // MARK: -- generates config correctly + it("generates config correctly") { + let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let identity = try! Identity.generate(from: seed) + var edSK: [UInt8] = identity.ed25519KeyPair.secretKey + expect(edSK.toHexString().suffix(64)) + .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) + expect(identity.x25519KeyPair.publicKey.toHexString()) + .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) + expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) + + // Initialize a brand new, empty config because we have no dump data to deal with. + let error: UnsafeMutablePointer? = nil + var conf: UnsafeMutablePointer? = nil + expect(user_profile_init(&conf, &edSK, nil, 0, error)).to(equal(0)) + error?.deallocate() + + // We don't need to push anything, since this is an empty config + expect(config_needs_push(conf)).to(beFalse()) + // And we haven't changed anything so don't need to dump to db + expect(config_needs_dump(conf)).to(beFalse()) + + // Since it's empty there shouldn't be a name. + let namePtr: UnsafePointer? = user_profile_get_name(conf) + expect(namePtr).to(beNil()) + + // We don't need to push since we haven't changed anything, so this call is mainly just for + // testing: + let pushData1: UnsafeMutablePointer = config_push(conf) + expect(pushData1.pointee).toNot(beNil()) + expect(pushData1.pointee.seqno).to(equal(0)) + expect(pushData1.pointee.config_len).to(equal(256)) + + let encDomain: [CChar] = "UserProfile" + .bytes + .map { CChar(bitPattern: $0) } + expect(String(cString: config_encryption_domain(conf))).to(equal("UserProfile")) + + var toPushDecSize: Int = 0 + let toPushDecrypted: UnsafeMutablePointer? = config_decrypt(pushData1.pointee.config, pushData1.pointee.config_len, edSK, encDomain, &toPushDecSize) + let prefixPadding: String = (0..<193) + .map { _ in "\0" } + .joined() + expect(toPushDecrypted).toNot(beNil()) + expect(toPushDecSize).to(equal(216)) // 256 - 40 overhead + expect(String(pointer: toPushDecrypted, length: toPushDecSize)) + .to(equal("\(prefixPadding)d1:#i0e1:&de1:? = user_profile_get_name(conf) + expect(namePtr2).toNot(beNil()) + expect(String(cString: namePtr2!)).to(equal("Kallie")) + + let pic2: user_profile_pic = user_profile_get_pic(conf); + expect(String(libSessionVal: pic2.url)).to(equal("http://example.org/omg-pic-123.bmp")) + expect(Data(libSessionVal: pic2.key, count: ProfileManager.avatarAES256KeyByteLength)) + .to(equal("secret78901234567890123456789012".data(using: .utf8))) + expect(user_profile_get_nts_priority(conf)).to(equal(9)) + + // Since we've made changes, we should need to push new config to the swarm, *and* should need + // to dump the updated state: + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + + // incremented since we made changes (this only increments once between + // dumps; even though we changed two fields here). + let pushData2: UnsafeMutablePointer = config_push(conf) + expect(pushData2.pointee.seqno).to(equal(1)) + + // Note: This hex value differs from the value in the library tests because + // it looks like the library has an "end of cell mark" character added at the + // end (0x07 or '0007') so we need to manually add it to work + let expHash0: [UInt8] = Data(hex: "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965") + .bytes + // The data to be actually pushed, expanded like this to make it somewhat human-readable: + let expPush1Decrypted: [UInt8] = [""" + d + 1:#i1e + 1:& d + 1:+ i9e + 1:n 6:Kallie + 1:p 34:http://example.org/omg-pic-123.bmp + 1:q 32:secret78901234567890123456789012 + e + 1:< l + l i0e 32: + """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability + .bytes, + expHash0, + """ + de e + e + 1:= d + 1:+ 0: + 1:n 0: + 1:p 0: + 1:q 0: + e + e + """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability + .bytes + ].flatMap { $0 } + let expPush1Encrypted: [UInt8] = Data(hex: [ + "9693a69686da3055f1ecdfb239c3bf8e746951a36d888c2fb7c02e856a5c2091b24e39a7e1af828f", + "1fa09fe8bf7d274afde0a0847ba143c43ffb8722301b5ae32e2f078b9a5e19097403336e50b18c84", + "aade446cd2823b011f97d6ad2116a53feb814efecc086bc172d31f4214b4d7c630b63bbe575b0868", + "2d146da44915063a07a78556ab5eff4f67f6aa26211e8d330b53d28567a931028c393709a325425d", + "e7486ccde24416a7fd4a8ba5fa73899c65f4276dfaddd5b2100adcf0f793104fb235b31ce32ec656", + "056009a9ebf58d45d7d696b74e0c7ff0499c4d23204976f19561dc0dba6dc53a2497d28ce03498ea", + "49bf122762d7bc1d6d9c02f6d54f8384" + ].joined()).bytes + + let pushData2Str: String = String(pointer: pushData2.pointee.config, length: pushData2.pointee.config_len, encoding: .ascii)! + let expPush1EncryptedStr: String = String(pointer: expPush1Encrypted, length: expPush1Encrypted.count, encoding: .ascii)! + expect(pushData2Str).to(equal(expPush1EncryptedStr)) + + // Raw decryption doesn't unpad (i.e. the padding is part of the encrypted data) + var pushData2DecSize: Int = 0 + let pushData2Decrypted: UnsafeMutablePointer? = config_decrypt( + pushData2.pointee.config, + pushData2.pointee.config_len, + edSK, + encDomain, + &pushData2DecSize + ) + let prefixPadding2: String = (0..<(256 - 40 - expPush1Decrypted.count)) + .map { _ in "\0" } + .joined() + expect(pushData2DecSize).to(equal(216)) // 256 - 40 overhead + + let pushData2DecryptedStr: String = String(pointer: pushData2Decrypted, length: pushData2DecSize, encoding: .ascii)! + let expPush1DecryptedStr: String = String(pointer: expPush1Decrypted, length: expPush1Decrypted.count, encoding: .ascii) + .map { "\(prefixPadding2)\($0)" }! + expect(pushData2DecryptedStr).to(equal(expPush1DecryptedStr)) + pushData2Decrypted?.deallocate() + + // We haven't dumped, so still need to dump: + expect(config_needs_dump(conf)).to(beTrue()) + // We did call push, but we haven't confirmed it as stored yet, so this will still return true: + expect(config_needs_push(conf)).to(beTrue()) + + var dump1: UnsafeMutablePointer? = nil + var dump1Len: Int = 0 + + config_dump(conf, &dump1, &dump1Len) + // (in a real client we'd now store this to disk) + + expect(config_needs_dump(conf)).to(beFalse()) + + let expDump1: [CChar] = [ + """ + d + 1:! i2e + 1:$ \(expPush1Decrypted.count): + """ + .removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + .bytes + .map { CChar(bitPattern: $0) }, + expPush1Decrypted + .map { CChar(bitPattern: $0) }, + """ + 1:(0: + 1:)le + e + """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + .bytes + .map { CChar(bitPattern: $0) } + ].flatMap { $0 } + expect(String(pointer: dump1, length: dump1Len, encoding: .ascii)) + .to(equal(String(pointer: expDump1, length: expDump1.count, encoding: .ascii))) + dump1?.deallocate() + + // So now imagine we got back confirmation from the swarm that the push has been stored: + let fakeHash1: String = "fakehash1" + var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() + config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) + pushData2.deallocate() + + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beTrue()) // The confirmation changes state, so this makes us need a dump + + var dump2: UnsafeMutablePointer? = nil + var dump2Len: Int = 0 + config_dump(conf, &dump2, &dump2Len) + + let expDump2: [CChar] = [ + """ + d + 1:! i0e + 1:$ \(expPush1Decrypted.count): + """ + .removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + .bytes + .map { CChar(bitPattern: $0) }, + expPush1Decrypted + .map { CChar(bitPattern: $0) }, + """ + 1:(9:fakehash1 + 1:)le + e + """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + .bytes + .map { CChar(bitPattern: $0) } + ].flatMap { $0 } + expect(String(pointer: dump2, length: dump2Len, encoding: .ascii)) + .to(equal(String(pointer: expDump2, length: expDump2.count, encoding: .ascii))) + dump2?.deallocate() + expect(config_needs_dump(conf)).to(beFalse()) + + // Now we're going to set up a second, competing config object (in the real world this would be + // another Session client somewhere). + + // Start with an empty config, as above: + let error2: UnsafeMutablePointer? = nil + var conf2: UnsafeMutablePointer? = nil + expect(user_profile_init(&conf2, &edSK, nil, 0, error2)).to(equal(0)) + expect(config_needs_dump(conf2)).to(beFalse()) + error2?.deallocate() + + // Now imagine we just pulled down the `exp_push1` string from the swarm; we merge it into + // conf2: + var mergeHashes: [UnsafePointer?] = [cFakeHash1].unsafeCopy() + var mergeData: [UnsafePointer?] = [expPush1Encrypted].unsafeCopy() + var mergeSize: [Int] = [expPush1Encrypted.count] + expect(config_merge(conf2, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) + mergeHashes.forEach { $0?.deallocate() } + mergeData.forEach { $0?.deallocate() } + + // Our state has changed, so we need to dump: + expect(config_needs_dump(conf2)).to(beTrue()) + var dump3: UnsafeMutablePointer? = nil + var dump3Len: Int = 0 + config_dump(conf2, &dump3, &dump3Len) + // (store in db) + dump3?.deallocate() + expect(config_needs_dump(conf2)).to(beFalse()) + + // We *don't* need to push: even though we updated, all we did is update to the merged data (and + // didn't have any sort of merge conflict needed): + expect(config_needs_push(conf2)).to(beFalse()) + + // Now let's create a conflicting update: + + // Change the name on both clients: + user_profile_set_name(conf, "Nibbler") + user_profile_set_name(conf2, "Raz") + + // And, on conf2, we're also going to change the profile pic: + let p2: user_profile_pic = user_profile_pic( + url: "http://new.example.com/pic".toLibSession(), + key: "qwert\0yuio1234567890123456789012".data(using: .utf8)!.toLibSession() + ) + user_profile_set_pic(conf2, p2) + + user_profile_set_nts_expiry(conf2, 86400) + expect(user_profile_get_nts_expiry(conf2)).to(equal(86400)) + + expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(-1)) + user_profile_set_blinded_msgreqs(conf2, 0) + expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(0)) + user_profile_set_blinded_msgreqs(conf2, -1) + expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(-1)) + user_profile_set_blinded_msgreqs(conf2, 1) + expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(1)) + + // Both have changes, so push need a push + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_push(conf2)).to(beTrue()) + + let fakeHash2: String = "fakehash2" + var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() + let pushData3: UnsafeMutablePointer = config_push(conf) + expect(pushData3.pointee.seqno).to(equal(2)) // incremented, since we made a field change + config_confirm_pushed(conf, pushData3.pointee.seqno, &cFakeHash2) + + let fakeHash3: String = "fakehash3" + var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated() + let pushData4: UnsafeMutablePointer = config_push(conf2) + expect(pushData4.pointee.seqno).to(equal(2)) // incremented, since we made a field change + config_confirm_pushed(conf, pushData4.pointee.seqno, &cFakeHash3) + + var dump4: UnsafeMutablePointer? = nil + var dump4Len: Int = 0 + config_dump(conf, &dump4, &dump4Len); + var dump5: UnsafeMutablePointer? = nil + var dump5Len: Int = 0 + config_dump(conf2, &dump5, &dump5Len); + // (store in db) + dump4?.deallocate() + dump5?.deallocate() + + // Since we set different things, we're going to get back different serialized data to be + // pushed: + let pushData3Str: String? = String(pointer: pushData3.pointee.config, length: pushData3.pointee.config_len, encoding: .ascii) + let pushData4Str: String? = String(pointer: pushData4.pointee.config, length: pushData4.pointee.config_len, encoding: .ascii) + expect(pushData3Str).toNot(equal(pushData4Str)) + + // Now imagine that each client pushed its `seqno=2` config to the swarm, but then each client + // also fetches new messages and pulls down the other client's `seqno=2` value. + + // Feed the new config into each other. (This array could hold multiple configs if we pulled + // down more than one). + var mergeHashes2: [UnsafePointer?] = [cFakeHash2].unsafeCopy() + var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData3.pointee.config)] + var mergeSize2: [Int] = [pushData3.pointee.config_len] + expect(config_merge(conf2, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) + pushData3.deallocate() + var mergeHashes3: [UnsafePointer?] = [cFakeHash3].unsafeCopy() + var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData4.pointee.config)] + var mergeSize3: [Int] = [pushData4.pointee.config_len] + expect(config_merge(conf, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) + pushData4.deallocate() + + // Now after the merge we *will* want to push from both client, since both will have generated a + // merge conflict update (with seqno = 3). + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_push(conf2)).to(beTrue()) + let pushData5: UnsafeMutablePointer = config_push(conf) + let pushData6: UnsafeMutablePointer = config_push(conf2) + expect(pushData5.pointee.seqno).to(equal(3)) + expect(pushData6.pointee.seqno).to(equal(3)) + + // They should have resolved the conflict to the same thing: + expect(String(cString: user_profile_get_name(conf)!)).to(equal("Nibbler")) + expect(String(cString: user_profile_get_name(conf2)!)).to(equal("Nibbler")) + // (Note that they could have also both resolved to "Raz" here, but the hash of the serialized + // message just happens to have a higher hash -- and thus gets priority -- for this particular + // test). + + // Since only one of them set a profile pic there should be no conflict there: + let pic3: user_profile_pic = user_profile_get_pic(conf) + expect(pic3.url).toNot(beNil()) + expect(String(libSessionVal: pic3.url)).to(equal("http://new.example.com/pic")) + expect(pic3.key).toNot(beNil()) + expect(Data(libSessionVal: pic3.key, count: 32).toHexString()) + .to(equal("7177657274007975696f31323334353637383930313233343536373839303132")) + let pic4: user_profile_pic = user_profile_get_pic(conf2) + expect(pic4.url).toNot(beNil()) + expect(String(libSessionVal: pic4.url)).to(equal("http://new.example.com/pic")) + expect(pic4.key).toNot(beNil()) + expect(Data(libSessionVal: pic4.key, count: 32).toHexString()) + .to(equal("7177657274007975696f31323334353637383930313233343536373839303132")) + expect(user_profile_get_nts_priority(conf)).to(equal(9)) + expect(user_profile_get_nts_priority(conf2)).to(equal(9)) + expect(user_profile_get_nts_expiry(conf)).to(equal(86400)) + expect(user_profile_get_nts_expiry(conf2)).to(equal(86400)) + expect(user_profile_get_blinded_msgreqs(conf)).to(equal(1)) + expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(1)) + + let fakeHash4: String = "fakehash4" + var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() + let fakeHash5: String = "fakehash5" + var cFakeHash5: [CChar] = fakeHash5.cArray.nullTerminated() + config_confirm_pushed(conf, pushData5.pointee.seqno, &cFakeHash4) + config_confirm_pushed(conf2, pushData6.pointee.seqno, &cFakeHash5) + pushData5.deallocate() + pushData6.deallocate() + + var dump6: UnsafeMutablePointer? = nil + var dump6Len: Int = 0 + config_dump(conf, &dump6, &dump6Len); + var dump7: UnsafeMutablePointer? = nil + var dump7Len: Int = 0 + config_dump(conf2, &dump7, &dump7Len); + // (store in db) + dump6?.deallocate() + dump7?.deallocate() + + expect(config_needs_dump(conf)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_push(conf2)).to(beFalse()) + + // Wouldn't do this in a normal session but doing it here to properly clean up + // after the test + conf?.deallocate() + conf2?.deallocate() + } + } + } +} + +// MARK: - CONVO_INFO_VOLATILE + +fileprivate extension LibSessionSpec { + class func convoInfoVolatileSpec() { + context("CONVO_INFO_VOLATILE") { + // MARK: -- generates config correctly + it("generates config correctly") { + let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let identity = try! Identity.generate(from: seed) + var edSK: [UInt8] = identity.ed25519KeyPair.secretKey + expect(edSK.toHexString().suffix(64)) + .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) + expect(identity.x25519KeyPair.publicKey.toHexString()) + .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) + expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) + + // Initialize a brand new, empty config because we have no dump data to deal with. + let error: UnsafeMutablePointer? = nil + var conf: UnsafeMutablePointer? = nil + expect(convo_info_volatile_init(&conf, &edSK, nil, 0, error)).to(equal(0)) + error?.deallocate() + + // Empty contacts shouldn't have an existing contact + let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000" + var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() + var oneToOne1: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_1to1(conf, &oneToOne1, &cDefinitelyRealId)).to(beFalse()) + expect(convo_info_volatile_size(conf)).to(equal(0)) + + var oneToOne2: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_or_construct_1to1(conf, &oneToOne2, &cDefinitelyRealId)) + .to(beTrue()) + expect(String(libSessionVal: oneToOne2.session_id)).to(equal(definitelyRealId)) + expect(oneToOne2.last_read).to(equal(0)) + expect(oneToOne2.unread).to(beFalse()) + + // No need to sync a conversation with a default state + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beFalse()) + + // Update the last read + let nowTimestampMs: Int64 = Int64(floor(Date().timeIntervalSince1970 * 1000)) + oneToOne2.last_read = nowTimestampMs + + // The new data doesn't get stored until we call this: + convo_info_volatile_set_1to1(conf, &oneToOne2) + + var legacyGroup1: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + var oneToOne3: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_legacy_group(conf, &legacyGroup1, &cDefinitelyRealId)) + .to(beFalse()) + expect(convo_info_volatile_get_1to1(conf, &oneToOne3, &cDefinitelyRealId)).to(beTrue()) + expect(oneToOne3.last_read).to(equal(nowTimestampMs)) + + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + + let openGroupBaseUrl: String = "http://Example.ORG:5678" + var cOpenGroupBaseUrl: [CChar] = openGroupBaseUrl.cArray.nullTerminated() + let openGroupBaseUrlResult: String = openGroupBaseUrl.lowercased() + // ("http://Example.ORG:5678" + // .lowercased() + // .cArray + + // [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count)) + // ) + let openGroupRoom: String = "SudokuRoom" + var cOpenGroupRoom: [CChar] = openGroupRoom.cArray.nullTerminated() + let openGroupRoomResult: String = openGroupRoom.lowercased() + // ("SudokuRoom" + // .lowercased() + // .cArray + + // [CChar](repeating: 0, count: (65 - openGroupRoom.count)) + // ) + var cOpenGroupPubkey: [UInt8] = Data(hex: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + .bytes + var community1: convo_info_volatile_community = convo_info_volatile_community() + expect(convo_info_volatile_get_or_construct_community(conf, &community1, &cOpenGroupBaseUrl, &cOpenGroupRoom, &cOpenGroupPubkey)).to(beTrue()) + expect(String(libSessionVal: community1.base_url)).to(equal(openGroupBaseUrlResult)) + expect(String(libSessionVal: community1.room)).to(equal(openGroupRoomResult)) + expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString()) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + community1.unread = true + + // The new data doesn't get stored until we call this: + convo_info_volatile_set_community(conf, &community1); + + // We don't need to push since we haven't changed anything, so this call is mainly just for + // testing: + let pushData1: UnsafeMutablePointer = config_push(conf) + expect(pushData1.pointee.seqno).to(equal(1)) + + // Pretend we uploaded it + let fakeHash1: String = "fakehash1" + var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() + config_confirm_pushed(conf, pushData1.pointee.seqno, &cFakeHash1) + expect(config_needs_dump(conf)).to(beTrue()) + expect(config_needs_push(conf)).to(beFalse()) + pushData1.deallocate() + + var dump1: UnsafeMutablePointer? = nil + var dump1Len: Int = 0 + config_dump(conf, &dump1, &dump1Len) + + let error2: UnsafeMutablePointer? = nil + var conf2: UnsafeMutablePointer? = nil + expect(convo_info_volatile_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) + error2?.deallocate() + dump1?.deallocate() + + expect(config_needs_dump(conf2)).to(beFalse()) + expect(config_needs_push(conf2)).to(beFalse()) + + var oneToOne4: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_1to1(conf2, &oneToOne4, &cDefinitelyRealId)).to(equal(true)) + expect(oneToOne4.last_read).to(equal(nowTimestampMs)) + expect(String(libSessionVal: oneToOne4.session_id)).to(equal(definitelyRealId)) + expect(oneToOne4.unread).to(beFalse()) + + var community2: convo_info_volatile_community = convo_info_volatile_community() + expect(convo_info_volatile_get_community(conf2, &community2, &cOpenGroupBaseUrl, &cOpenGroupRoom)).to(beTrue()) + expect(String(libSessionVal: community2.base_url)).to(equal(openGroupBaseUrlResult)) + expect(String(libSessionVal: community2.room)).to(equal(openGroupRoomResult)) + expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString()) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + community2.unread = true + + let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" + var cAnotherId: [CChar] = anotherId.cArray.nullTerminated() + var oneToOne5: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_or_construct_1to1(conf2, &oneToOne5, &cAnotherId)).to(beTrue()) + oneToOne5.unread = true + convo_info_volatile_set_1to1(conf2, &oneToOne5) + + let thirdId: String = "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + var cThirdId: [CChar] = thirdId.cArray.nullTerminated() + var legacyGroup2: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + expect(convo_info_volatile_get_or_construct_legacy_group(conf2, &legacyGroup2, &cThirdId)).to(beTrue()) + legacyGroup2.last_read = (nowTimestampMs - 50) + convo_info_volatile_set_legacy_group(conf2, &legacyGroup2) + expect(config_needs_push(conf2)).to(beTrue()) + + let pushData2: UnsafeMutablePointer = config_push(conf2) + expect(pushData2.pointee.seqno).to(equal(2)) + + // Check the merging + let fakeHash2: String = "fakehash2" + var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() + var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() + var mergeData: [UnsafePointer?] = [UnsafePointer(pushData2.pointee.config)] + var mergeSize: [Int] = [pushData2.pointee.config_len] + expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) + config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash2) + pushData2.deallocate() + + expect(config_needs_push(conf)).to(beFalse()) + + for targetConf in [conf, conf2] { + // Iterate through and make sure we got everything we expected + var seen: [String] = [] + expect(convo_info_volatile_size(conf)).to(equal(4)) + expect(convo_info_volatile_size_1to1(conf)).to(equal(2)) + expect(convo_info_volatile_size_communities(conf)).to(equal(1)) + expect(convo_info_volatile_size_legacy_groups(conf)).to(equal(1)) + + var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1() + var c2: convo_info_volatile_community = convo_info_volatile_community() + var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + let it: OpaquePointer = convo_info_volatile_iterator_new(targetConf) + + while !convo_info_volatile_iterator_done(it) { + if convo_info_volatile_it_is_1to1(it, &c1) { + seen.append("1-to-1: \(String(libSessionVal: c1.session_id))") + } + else if convo_info_volatile_it_is_community(it, &c2) { + seen.append("og: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") + } + else if convo_info_volatile_it_is_legacy_group(it, &c3) { + seen.append("cl: \(String(libSessionVal: c3.group_id))") + } + + convo_info_volatile_iterator_advance(it) + } + + convo_info_volatile_iterator_free(it) + + expect(seen).to(equal([ + "1-to-1: 051111111111111111111111111111111111111111111111111111111111111111", + "1-to-1: 055000000000000000000000000000000000000000000000000000000000000000", + "og: http://example.org:5678/r/sudokuroom", + "cl: 05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ])) + } + + let fourthId: String = "052000000000000000000000000000000000000000000000000000000000000000" + var cFourthId: [CChar] = fourthId.cArray.nullTerminated() + expect(config_needs_push(conf)).to(beFalse()) + convo_info_volatile_erase_1to1(conf, &cFourthId) + expect(config_needs_push(conf)).to(beFalse()) + convo_info_volatile_erase_1to1(conf, &cDefinitelyRealId) + expect(config_needs_push(conf)).to(beTrue()) + expect(convo_info_volatile_size(conf)).to(equal(3)) + expect(convo_info_volatile_size_1to1(conf)).to(equal(1)) + + // Check the single-type iterators: + var seen1: [String?] = [] + var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1() + let it1: OpaquePointer = convo_info_volatile_iterator_new_1to1(conf) + + while !convo_info_volatile_iterator_done(it1) { + expect(convo_info_volatile_it_is_1to1(it1, &c1)).to(beTrue()) + + seen1.append(String(libSessionVal: c1.session_id)) + convo_info_volatile_iterator_advance(it1) + } + + convo_info_volatile_iterator_free(it1) + expect(seen1).to(equal([ + "051111111111111111111111111111111111111111111111111111111111111111" + ])) + + var seen2: [String?] = [] + var c2: convo_info_volatile_community = convo_info_volatile_community() + let it2: OpaquePointer = convo_info_volatile_iterator_new_communities(conf) + + while !convo_info_volatile_iterator_done(it2) { + expect(convo_info_volatile_it_is_community(it2, &c2)).to(beTrue()) + + seen2.append(String(libSessionVal: c2.base_url)) + convo_info_volatile_iterator_advance(it2) + } + + convo_info_volatile_iterator_free(it2) + expect(seen2).to(equal([ + "http://example.org:5678" + ])) + + var seen3: [String?] = [] + var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + let it3: OpaquePointer = convo_info_volatile_iterator_new_legacy_groups(conf) + + while !convo_info_volatile_iterator_done(it3) { + expect(convo_info_volatile_it_is_legacy_group(it3, &c3)).to(beTrue()) + + seen3.append(String(libSessionVal: c3.group_id)) + convo_info_volatile_iterator_advance(it3) + } + + convo_info_volatile_iterator_free(it3) + expect(seen3).to(equal([ + "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ])) + } + } + } +} + +// MARK: - USER_GROUPS + +fileprivate extension LibSessionSpec { + class func userGroupsSpec() { + context("USER_GROUPS") { + // MARK: -- generates config correctly + it("generates config correctly") { + let createdTs: Int64 = 1680064059 + let nowTs: Int64 = Int64(Date().timeIntervalSince1970) + let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let identity = try! Identity.generate(from: seed) + var edSK: [UInt8] = identity.ed25519KeyPair.secretKey + expect(edSK.toHexString().suffix(64)) + .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) + expect(identity.x25519KeyPair.publicKey.toHexString()) + .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) + expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) + + // Initialize a brand new, empty config because we have no dump data to deal with. + let error: UnsafeMutablePointer? = nil + var conf: UnsafeMutablePointer? = nil + expect(user_groups_init(&conf, &edSK, nil, 0, error)).to(equal(0)) + error?.deallocate() + + // Empty contacts shouldn't have an existing contact + let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000" + var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() + let legacyGroup1: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cDefinitelyRealId) + expect(legacyGroup1?.pointee).to(beNil()) + expect(user_groups_size(conf)).to(equal(0)) + + let legacyGroup2: UnsafeMutablePointer = user_groups_get_or_construct_legacy_group(conf, &cDefinitelyRealId) + expect(legacyGroup2.pointee).toNot(beNil()) + expect(String(libSessionVal: legacyGroup2.pointee.session_id)) + .to(equal(definitelyRealId)) + expect(legacyGroup2.pointee.disappearing_timer).to(equal(0)) + expect(String(libSessionVal: legacyGroup2.pointee.enc_pubkey, fixedLength: 32)).to(equal("")) + expect(String(libSessionVal: legacyGroup2.pointee.enc_seckey, fixedLength: 32)).to(equal("")) + expect(legacyGroup2.pointee.priority).to(equal(0)) + expect(String(libSessionVal: legacyGroup2.pointee.name)).to(equal("")) + expect(legacyGroup2.pointee.joined_at).to(equal(0)) + expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_DEFAULT)) + expect(legacyGroup2.pointee.mute_until).to(equal(0)) + + // Iterate through and make sure we got everything we expected + var membersSeen1: [String: Bool] = [:] + var memberSessionId1: UnsafePointer? = nil + var memberAdmin1: Bool = false + let membersIt1: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2) + + while ugroups_legacy_members_next(membersIt1, &memberSessionId1, &memberAdmin1) { + membersSeen1[String(cString: memberSessionId1!)] = memberAdmin1 + } + + ugroups_legacy_members_free(membersIt1) + + expect(membersSeen1).to(beEmpty()) + + // No need to sync a conversation with a default state + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beFalse()) + + // We don't need to push since we haven't changed anything, so this call is mainly just for + // testing: + let pushData1: UnsafeMutablePointer = config_push(conf) + expect(pushData1.pointee.seqno).to(equal(0)) + expect([String](pointer: pushData1.pointee.obsolete, count: pushData1.pointee.obsolete_len)) + .to(beEmpty()) + expect(pushData1.pointee.config_len).to(equal(256)) + pushData1.deallocate() + + let users: [String] = [ + "050000000000000000000000000000000000000000000000000000000000000000", + "051111111111111111111111111111111111111111111111111111111111111111", + "052222222222222222222222222222222222222222222222222222222222222222", + "053333333333333333333333333333333333333333333333333333333333333333", + "054444444444444444444444444444444444444444444444444444444444444444", + "055555555555555555555555555555555555555555555555555555555555555555", + "056666666666666666666666666666666666666666666666666666666666666666" + ] + var cUsers: [[CChar]] = users.map { $0.cArray.nullTerminated() } + legacyGroup2.pointee.name = "Englishmen".toLibSession() + legacyGroup2.pointee.disappearing_timer = 60 + legacyGroup2.pointee.joined_at = createdTs + legacyGroup2.pointee.notifications = CONVO_NOTIFY_ALL + legacyGroup2.pointee.mute_until = (nowTs + 3600) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[0], false)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], true)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[4], true)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[5], false)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beFalse()) + + // Flip to and from admin + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], true)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], false)).to(beTrue()) + + expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[5])).to(beTrue()) + expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[4])).to(beTrue()) + + var membersSeen2: [String: Bool] = [:] + var memberSessionId2: UnsafePointer? = nil + var memberAdmin2: Bool = false + let membersIt2: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2) + + while ugroups_legacy_members_next(membersIt2, &memberSessionId2, &memberAdmin2) { + membersSeen2[String(cString: memberSessionId2!)] = memberAdmin2 + } + + ugroups_legacy_members_free(membersIt2) + + expect(membersSeen2).to(equal([ + "050000000000000000000000000000000000000000000000000000000000000000": false, + "051111111111111111111111111111111111111111111111111111111111111111": false, + "052222222222222222222222222222222222222222222222222222222222222222": true + ])) + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let groupSeed: Data = Data(hex: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff") + let groupEd25519KeyPair = Sodium().sign.keyPair(seed: groupSeed.bytes)! + let groupX25519PublicKey = Sodium().sign.toX25519(ed25519PublicKey: groupEd25519KeyPair.publicKey)! + + // Note: this isn't exactly what Session actually does here for legacy closed + // groups (rather it uses X25519 keys) but for this test the distinction doesn't matter. + legacyGroup2.pointee.enc_pubkey = Data(groupX25519PublicKey).toLibSession() + legacyGroup2.pointee.enc_seckey = Data(groupEd25519KeyPair.secretKey).toLibSession() + legacyGroup2.pointee.priority = 3 + + expect(Data(libSessionVal: legacyGroup2.pointee.enc_pubkey, count: 32).toHexString()) + .to(equal("c5ba413c336f2fe1fb9a2c525f8a86a412a1db128a7841b4e0e217fa9eb7fd5e")) + expect(Data(libSessionVal: legacyGroup2.pointee.enc_seckey, count: 32).toHexString()) + .to(equal("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff")) + + // The new data doesn't get stored until we call this: + user_groups_set_free_legacy_group(conf, legacyGroup2) + + let legacyGroup3: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cDefinitelyRealId) + expect(legacyGroup3?.pointee).toNot(beNil()) + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + ugroups_legacy_group_free(legacyGroup3) + + let communityPubkey: String = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + var cCommunityPubkey: [UInt8] = Data(hex: communityPubkey).cArray + var cCommunityBaseUrl: [CChar] = "http://Example.ORG:5678".cArray.nullTerminated() + var cCommunityRoom: [CChar] = "SudokuRoom".cArray.nullTerminated() + var community1: ugroups_community_info = ugroups_community_info() + expect(user_groups_get_or_construct_community(conf, &community1, &cCommunityBaseUrl, &cCommunityRoom, &cCommunityPubkey)) + .to(beTrue()) + + expect(String(libSessionVal: community1.base_url)).to(equal("http://example.org:5678")) // Note: lower-case + expect(String(libSessionVal: community1.room)).to(equal("SudokuRoom")) // Note: case-preserving + expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString()) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + community1.priority = 14 + + // The new data doesn't get stored until we call this: + user_groups_set_community(conf, &community1) + + // incremented since we made changes (this only increments once between + // dumps; even though we changed two fields here). + let pushData2: UnsafeMutablePointer = config_push(conf) + expect(pushData2.pointee.seqno).to(equal(1)) + expect([String](pointer: pushData2.pointee.obsolete, count: pushData2.pointee.obsolete_len)) + .to(beEmpty()) + + // Pretend we uploaded it + let fakeHash1: String = "fakehash1" + var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() + config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) + expect(config_needs_dump(conf)).to(beTrue()) + expect(config_needs_push(conf)).to(beFalse()) + + var dump1: UnsafeMutablePointer? = nil + var dump1Len: Int = 0 + config_dump(conf, &dump1, &dump1Len) + + let error2: UnsafeMutablePointer? = nil + var conf2: UnsafeMutablePointer? = nil + expect(user_groups_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) + error2?.deallocate() + dump1?.deallocate() + + expect(config_needs_dump(conf)).to(beFalse()) // Because we just called dump() above, to load up conf2 + expect(config_needs_push(conf)).to(beFalse()) + + let pushData3: UnsafeMutablePointer = config_push(conf) + expect(pushData3.pointee.seqno).to(equal(1)) + expect([String](pointer: pushData3.pointee.obsolete, count: pushData3.pointee.obsolete_len)) + .to(beEmpty()) + pushData3.deallocate() + + let currentHashes1: UnsafeMutablePointer? = config_current_hashes(conf) + expect([String](pointer: currentHashes1?.pointee.value, count: currentHashes1?.pointee.len)) + .to(equal(["fakehash1"])) + currentHashes1?.deallocate() + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData4: UnsafeMutablePointer = config_push(conf2) + expect(pushData4.pointee.seqno).to(equal(1)) + expect(config_needs_dump(conf2)).to(beFalse()) + expect([String](pointer: pushData4.pointee.obsolete, count: pushData4.pointee.obsolete_len)) + .to(beEmpty()) + pushData4.deallocate() + + let currentHashes2: UnsafeMutablePointer? = config_current_hashes(conf2) + expect([String](pointer: currentHashes2?.pointee.value, count: currentHashes2?.pointee.len)) + .to(equal(["fakehash1"])) + currentHashes2?.deallocate() + + expect(user_groups_size(conf2)).to(equal(2)) + expect(user_groups_size_communities(conf2)).to(equal(1)) + expect(user_groups_size_legacy_groups(conf2)).to(equal(1)) + + let legacyGroup4: UnsafeMutablePointer? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId) + expect(legacyGroup4?.pointee).toNot(beNil()) + expect(String(libSessionVal: legacyGroup4?.pointee.enc_pubkey, fixedLength: 32)).to(equal("")) + expect(String(libSessionVal: legacyGroup4?.pointee.enc_seckey, fixedLength: 32)).to(equal("")) + expect(legacyGroup4?.pointee.disappearing_timer).to(equal(60)) + expect(String(libSessionVal: legacyGroup4?.pointee.session_id)).to(equal(definitelyRealId)) + expect(legacyGroup4?.pointee.priority).to(equal(3)) + expect(String(libSessionVal: legacyGroup4?.pointee.name)).to(equal("Englishmen")) + expect(legacyGroup4?.pointee.joined_at).to(equal(createdTs)) + expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_ALL)) + expect(legacyGroup2.pointee.mute_until).to(equal(nowTs + 3600)) + + var membersSeen3: [String: Bool] = [:] + var memberSessionId3: UnsafePointer? = nil + var memberAdmin3: Bool = false + let membersIt3: OpaquePointer = ugroups_legacy_members_begin(legacyGroup4) + + while ugroups_legacy_members_next(membersIt3, &memberSessionId3, &memberAdmin3) { + membersSeen3[String(cString: memberSessionId3!)] = memberAdmin3 + } + + ugroups_legacy_members_free(membersIt3) + ugroups_legacy_group_free(legacyGroup4) + + expect(membersSeen3).to(equal([ + "050000000000000000000000000000000000000000000000000000000000000000": false, + "051111111111111111111111111111111111111111111111111111111111111111": false, + "052222222222222222222222222222222222222222222222222222222222222222": true + ])) + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData5: UnsafeMutablePointer = config_push(conf2) + expect(pushData5.pointee.seqno).to(equal(1)) + expect(config_needs_dump(conf2)).to(beFalse()) + pushData5.deallocate() + + for targetConf in [conf, conf2] { + // Iterate through and make sure we got everything we expected + var seen: [String] = [] + + var c1: ugroups_legacy_group_info = ugroups_legacy_group_info() + var c2: ugroups_community_info = ugroups_community_info() + let it: OpaquePointer = user_groups_iterator_new(targetConf) + + while !user_groups_iterator_done(it) { + if user_groups_it_is_legacy_group(it, &c1) { + var memberCount: Int = 0 + var adminCount: Int = 0 + ugroups_legacy_members_count(&c1, &memberCount, &adminCount) + seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members") + } + else if user_groups_it_is_community(it, &c2) { + seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") + } + else { + seen.append("unknown") + } + + user_groups_iterator_advance(it) + } + + user_groups_iterator_free(it) + + expect(seen).to(equal([ + "community: http://example.org:5678/r/SudokuRoom", + "legacy: Englishmen, 1 admins, 2 members" + ])) + } + + var cCommunity2BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated() + var cCommunity2Room: [CChar] = "sudokuRoom".cArray.nullTerminated() + var community2: ugroups_community_info = ugroups_community_info() + expect(user_groups_get_community(conf2, &community2, &cCommunity2BaseUrl, &cCommunity2Room)) + .to(beTrue()) + expect(String(libSessionVal: community2.base_url)).to(equal("http://example.org:5678")) + expect(String(libSessionVal: community2.room)).to(equal("SudokuRoom")) // Case preserved from the stored value, not the input value + expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString()) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(community2.priority).to(equal(14)) + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData6: UnsafeMutablePointer = config_push(conf2) + expect(pushData6.pointee.seqno).to(equal(1)) + expect(config_needs_dump(conf2)).to(beFalse()) + pushData6.deallocate() + + community2.room = "sudokuRoom".toLibSession() // Change capitalization + user_groups_set_community(conf2, &community2) + + expect(config_needs_push(conf2)).to(beTrue()) + expect(config_needs_dump(conf2)).to(beTrue()) + + let fakeHash2: String = "fakehash2" + var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() + let pushData7: UnsafeMutablePointer = config_push(conf2) + expect(pushData7.pointee.seqno).to(equal(2)) + config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash2) + expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len)) + .to(equal([fakeHash1])) + + let currentHashes3: UnsafeMutablePointer? = config_current_hashes(conf2) + expect([String](pointer: currentHashes3?.pointee.value, count: currentHashes3?.pointee.len)) + .to(equal([fakeHash2])) + currentHashes3?.deallocate() + + var dump2: UnsafeMutablePointer? = nil + var dump2Len: Int = 0 + config_dump(conf2, &dump2, &dump2Len) + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData8: UnsafeMutablePointer = config_push(conf2) + expect(pushData8.pointee.seqno).to(equal(2)) + config_confirm_pushed(conf2, pushData8.pointee.seqno, &cFakeHash2) + expect(config_needs_dump(conf2)).to(beFalse()) + + var mergeHashes1: [UnsafePointer?] = [cFakeHash2].unsafeCopy() + var mergeData1: [UnsafePointer?] = [UnsafePointer(pushData8.pointee.config)] + var mergeSize1: [Int] = [pushData8.pointee.config_len] + expect(config_merge(conf, &mergeHashes1, &mergeData1, &mergeSize1, 1)).to(equal(1)) + pushData8.deallocate() + + var cCommunity3BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated() + var cCommunity3Room: [CChar] = "SudokuRoom".cArray.nullTerminated() + var community3: ugroups_community_info = ugroups_community_info() + expect(user_groups_get_community(conf, &community3, &cCommunity3BaseUrl, &cCommunity3Room)) + .to(beTrue()) + expect(String(libSessionVal: community3.room)).to(equal("sudokuRoom")) // We picked up the capitalization change + + expect(user_groups_size(conf)).to(equal(2)) + expect(user_groups_size_communities(conf)).to(equal(1)) + expect(user_groups_size_legacy_groups(conf)).to(equal(1)) + + let legacyGroup5: UnsafeMutablePointer? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId) + expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[4], false)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[5], true)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[6], true)).to(beTrue()) + expect(ugroups_legacy_member_remove(legacyGroup5, &cUsers[1])).to(beTrue()) + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData9: UnsafeMutablePointer = config_push(conf2) + expect(pushData9.pointee.seqno).to(equal(2)) + expect(config_needs_dump(conf2)).to(beFalse()) + pushData9.deallocate() + + user_groups_set_free_legacy_group(conf2, legacyGroup5) + expect(config_needs_push(conf2)).to(beTrue()) + expect(config_needs_dump(conf2)).to(beTrue()) + + var cCommunity4BaseUrl: [CChar] = "http://exAMple.ORG:5678".cArray.nullTerminated() + var cCommunity4Room: [CChar] = "sudokuROOM".cArray.nullTerminated() + user_groups_erase_community(conf2, &cCommunity4BaseUrl, &cCommunity4Room) + + let fakeHash3: String = "fakehash3" + var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated() + let pushData10: UnsafeMutablePointer = config_push(conf2) + config_confirm_pushed(conf2, pushData10.pointee.seqno, &cFakeHash3) + + expect(pushData10.pointee.seqno).to(equal(3)) + expect([String](pointer: pushData10.pointee.obsolete, count: pushData10.pointee.obsolete_len)) + .to(equal([fakeHash2])) + + let currentHashes4: UnsafeMutablePointer? = config_current_hashes(conf2) + expect([String](pointer: currentHashes4?.pointee.value, count: currentHashes4?.pointee.len)) + .to(equal([fakeHash3])) + currentHashes4?.deallocate() + + var mergeHashes2: [UnsafePointer?] = [cFakeHash3].unsafeCopy() + var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData10.pointee.config)] + var mergeSize2: [Int] = [pushData10.pointee.config_len] + expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) + + expect(user_groups_size(conf)).to(equal(1)) + expect(user_groups_size_communities(conf)).to(equal(0)) + expect(user_groups_size_legacy_groups(conf)).to(equal(1)) + + var prio: Int32 = 0 + var cBeanstalkBaseUrl: [CChar] = "http://jacksbeanstalk.org".cArray.nullTerminated() + var cBeanstalkPubkey: [UInt8] = Data( + hex: "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff" + ).cArray + + ["fee", "fi", "fo", "fum"].forEach { room in + var cRoom: [CChar] = room.cArray.nullTerminated() + prio += 1 + + var community4: ugroups_community_info = ugroups_community_info() + expect(user_groups_get_or_construct_community(conf, &community4, &cBeanstalkBaseUrl, &cRoom, &cBeanstalkPubkey)) + .to(beTrue()) + community4.priority = prio + user_groups_set_community(conf, &community4) + } + + expect(user_groups_size(conf)).to(equal(5)) + expect(user_groups_size_communities(conf)).to(equal(4)) + expect(user_groups_size_legacy_groups(conf)).to(equal(1)) + + let fakeHash4: String = "fakehash4" + var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() + let pushData11: UnsafeMutablePointer = config_push(conf) + config_confirm_pushed(conf, pushData11.pointee.seqno, &cFakeHash4) + expect(pushData11.pointee.seqno).to(equal(4)) + expect([String](pointer: pushData11.pointee.obsolete, count: pushData11.pointee.obsolete_len)) + .to(equal([fakeHash3, fakeHash2, fakeHash1])) + + // Load some obsolete ones in just to check that they get immediately obsoleted + let fakeHash10: String = "fakehash10" + let cFakeHash10: [CChar] = fakeHash10.cArray.nullTerminated() + let fakeHash11: String = "fakehash11" + let cFakeHash11: [CChar] = fakeHash11.cArray.nullTerminated() + let fakeHash12: String = "fakehash12" + let cFakeHash12: [CChar] = fakeHash12.cArray.nullTerminated() + var mergeHashes3: [UnsafePointer?] = [cFakeHash10, cFakeHash11, cFakeHash12, cFakeHash4].unsafeCopy() + var mergeData3: [UnsafePointer?] = [ + UnsafePointer(pushData10.pointee.config), + UnsafePointer(pushData2.pointee.config), + UnsafePointer(pushData7.pointee.config), + UnsafePointer(pushData11.pointee.config) + ] + var mergeSize3: [Int] = [ + pushData10.pointee.config_len, + pushData2.pointee.config_len, + pushData7.pointee.config_len, + pushData11.pointee.config_len + ] + expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 4)).to(equal(4)) + expect(config_needs_dump(conf2)).to(beTrue()) + expect(config_needs_push(conf2)).to(beFalse()) + pushData2.deallocate() + pushData7.deallocate() + pushData10.deallocate() + pushData11.deallocate() + + let currentHashes5: UnsafeMutablePointer? = config_current_hashes(conf2) + expect([String](pointer: currentHashes5?.pointee.value, count: currentHashes5?.pointee.len)) + .to(equal([fakeHash4])) + currentHashes5?.deallocate() + + let pushData12: UnsafeMutablePointer = config_push(conf2) + expect(pushData12.pointee.seqno).to(equal(4)) + expect([String](pointer: pushData12.pointee.obsolete, count: pushData12.pointee.obsolete_len)) + .to(equal([fakeHash11, fakeHash12, fakeHash10, fakeHash3])) + pushData12.deallocate() + + for targetConf in [conf, conf2] { + // Iterate through and make sure we got everything we expected + var seen: [String] = [] + + var c1: ugroups_legacy_group_info = ugroups_legacy_group_info() + var c2: ugroups_community_info = ugroups_community_info() + let it: OpaquePointer = user_groups_iterator_new(targetConf) + + while !user_groups_iterator_done(it) { + if user_groups_it_is_legacy_group(it, &c1) { + var memberCount: Int = 0 + var adminCount: Int = 0 + ugroups_legacy_members_count(&c1, &memberCount, &adminCount) + + seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members") + } + else if user_groups_it_is_community(it, &c2) { + seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") + } + else { + seen.append("unknown") + } + + user_groups_iterator_advance(it) + } + + user_groups_iterator_free(it) + + expect(seen).to(equal([ + "community: http://jacksbeanstalk.org/r/fee", + "community: http://jacksbeanstalk.org/r/fi", + "community: http://jacksbeanstalk.org/r/fo", + "community: http://jacksbeanstalk.org/r/fum", + "legacy: Englishmen, 3 admins, 2 members" + ])) + } + } } } } diff --git a/SessionMessagingKitTests/LibSessionUtil/SessionUtilSpec.swift b/SessionMessagingKitTests/LibSessionUtil/SessionUtilSpec.swift index e8ac18129..f60ec1e79 100644 --- a/SessionMessagingKitTests/LibSessionUtil/SessionUtilSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/SessionUtilSpec.swift @@ -12,13 +12,12 @@ import Quick import Nimble class SessionUtilSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - SessionUtil describe("SessionUtil") { - // MARK: - Parsing URLs - + // MARK: -- when parsing a community url context("when parsing a community url") { + // MARK: ---- handles the example urls correctly it("handles the example urls correctly") { let validUrls: [String] = [ [ @@ -82,6 +81,7 @@ class SessionUtilSpec: QuickSpec { expect(processedPublicKeys).to(equal(expectedPublicKeys)) } + // MARK: ---- handles the r prefix if present it("handles the r prefix if present") { let info = SessionUtil.parseCommunity( url: [ @@ -95,6 +95,7 @@ class SessionUtilSpec: QuickSpec { expect(info?.publicKey).to(equal("658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c")) } + // MARK: ---- fails if no scheme is provided it("fails if no scheme is provided") { let info = SessionUtil.parseCommunity( url: [ @@ -108,6 +109,7 @@ class SessionUtilSpec: QuickSpec { expect(info?.publicKey).to(beNil()) } + // MARK: ---- fails if there is no room it("fails if there is no room") { let info = SessionUtil.parseCommunity( url: [ @@ -121,6 +123,7 @@ class SessionUtilSpec: QuickSpec { expect(info?.publicKey).to(beNil()) } + // MARK: ---- fails if there is no public key parameter it("fails if there is no public key parameter") { let info = SessionUtil.parseCommunity( url: "https://sessionopengroup.co/r/main" @@ -131,6 +134,7 @@ class SessionUtilSpec: QuickSpec { expect(info?.publicKey).to(beNil()) } + // MARK: ---- fails if the public key parameter is not 64 characters it("fails if the public key parameter is not 64 characters") { let info = SessionUtil.parseCommunity( url: [ @@ -144,6 +148,7 @@ class SessionUtilSpec: QuickSpec { expect(info?.publicKey).to(beNil()) } + // MARK: ---- fails if the public key parameter is not a hex string it("fails if the public key parameter is not a hex string") { let info = SessionUtil.parseCommunity( url: [ @@ -157,6 +162,7 @@ class SessionUtilSpec: QuickSpec { expect(info?.publicKey).to(beNil()) } + // MARK: ---- maintains the same TLS it("maintains the same TLS") { let server1 = SessionUtil.parseCommunity( url: [ @@ -175,6 +181,7 @@ class SessionUtilSpec: QuickSpec { expect(server2).to(equal("https://sessionopengroup.co")) } + // MARK: ---- maintains the same port it("maintains the same port") { let server1 = SessionUtil.parseCommunity( url: [ @@ -194,14 +201,15 @@ class SessionUtilSpec: QuickSpec { } } - // MARK: - Generating URLs - + // MARK: -- when generating a url context("when generating a url") { + // MARK: ---- generates the url correctly it("generates the url correctly") { expect(SessionUtil.communityUrlFor(server: "server", roomToken: "room", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000")) .to(equal("server/room?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000")) } + // MARK: ---- maintains the casing provided it("maintains the casing provided") { expect(SessionUtil.communityUrlFor(server: "SeRVer", roomToken: "RoOM", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000")) .to(equal("SeRVer/RoOM?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000")) diff --git a/SessionMessagingKitTests/LibSessionUtil/Utilities/LibSessionTypeConversionUtilitiesSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Utilities/LibSessionTypeConversionUtilitiesSpec.swift index 5aeeff2b3..313372b5f 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Utilities/LibSessionTypeConversionUtilitiesSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Utilities/LibSessionTypeConversionUtilitiesSpec.swift @@ -9,16 +9,15 @@ import Nimble @testable import SessionMessagingKit class LibSessionTypeConversionUtilitiesSpec: QuickSpec { - // MARK: - Spec - - override func spec() { - // MARK: - String - + override class func spec() { + // MARK: - a String describe("a String") { + // MARK: -- can convert to a cArray it("can convert to a cArray") { expect("Test123".cArray).to(equal([84, 101, 115, 116, 49, 50, 51])) } + // MARK: -- can contain emoji it("can contain emoji") { let original: String = "Hi 👋" let libSessionVal: (CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar) = original.toLibSession() @@ -27,7 +26,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal(original)) } + // MARK: -- when initialised with a pointer and length context("when initialised with a pointer and length") { + // MARK: ---- returns null when given a null pointer it("returns null when given a null pointer") { let test: [CChar] = [84, 101, 115, 116] let result = test.withUnsafeBufferPointer { ptr in @@ -37,6 +38,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(beNil()) } + // MARK: ---- returns a truncated string when given an incorrect length it("returns a truncated string when given an incorrect length") { let test: [CChar] = [84, 101, 115, 116] let result = test.withUnsafeBufferPointer { ptr in @@ -46,6 +48,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal("Te")) } + // MARK: ---- returns a string when valid it("returns a string when valid") { let test: [CChar] = [84, 101, 115, 116] let result = test.withUnsafeBufferPointer { ptr in @@ -56,7 +59,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { } } + // MARK: -- when initialised with a libSession value context("when initialised with a libSession value") { + // MARK: ---- returns a string when valid and has no fixed length it("returns a string when valid and has no fixed length") { let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 0) let result = String(libSessionVal: value, fixedLength: .none) @@ -64,6 +69,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal("Test")) } + // MARK: ---- returns a string when valid and has a fixed length it("returns a string when valid and has a fixed length") { let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116) let result = String(libSessionVal: value, fixedLength: 5) @@ -71,6 +77,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal("Te\0st")) } + // MARK: ---- truncates at the first null termination character when fixed length is none it("truncates at the first null termination character when fixed length is none") { let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116) let result = String(libSessionVal: value, fixedLength: .none) @@ -78,6 +85,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal("Te")) } + // MARK: ---- parses successfully if there is no null termination character and there is no fixed length it("parses successfully if there is no null termination character and there is no fixed length") { let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 84) let result = String(libSessionVal: value, fixedLength: .none) @@ -85,6 +93,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal("TestT")) } + // MARK: ---- returns an empty string when given a value only containing null termination characters with a fixed length it("returns an empty string when given a value only containing null termination characters with a fixed length") { let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) let result = String(libSessionVal: value, fixedLength: 5) @@ -92,6 +101,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal("")) } + // MARK: ---- defaults the fixed length value to none it("defaults the fixed length value to none") { let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 0, 0) let result = String(libSessionVal: value) @@ -99,6 +109,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal("Te")) } + // MARK: ---- returns an empty string when null and not set to return null it("returns an empty string when null and not set to return null") { let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) let result = String(libSessionVal: value, nullIfEmpty: false) @@ -106,6 +117,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal("")) } + // MARK: ---- returns null when specified and empty it("returns null when specified and empty") { let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) let result = String(libSessionVal: value, nullIfEmpty: true) @@ -113,6 +125,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(beNil()) } + // MARK: ---- defaults the null if empty flag to false it("defaults the null if empty flag to false") { let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) let result = String(libSessionVal: value) @@ -121,7 +134,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { } } + // MARK: -- when converting to a libSession value context("when converting to a libSession value") { + // MARK: ---- succeeeds with a valid value it("succeeeds with a valid value") { let result: (CChar, CChar, CChar, CChar, CChar) = "Test".toLibSession() expect(result.0).to(equal(84)) @@ -131,6 +146,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result.4).to(equal(0)) } + // MARK: ---- truncates when too long it("truncates when too long") { let result: (CChar, CChar, CChar, CChar, CChar) = "TestTest".toLibSession() expect(result.0).to(equal(84)) @@ -140,7 +156,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result.4).to(equal(84)) } + // MARK: ---- when optional context("when optional") { + // MARK: ------ returns empty when null context("returns empty when null") { let value: String? = nil let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession() @@ -152,6 +170,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result.4).to(equal(0)) } + // MARK: ------ returns a libSession value when not null context("returns a libSession value when not null") { let value: String? = "Test" let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession() @@ -167,13 +186,15 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { } // MARK: - Data - describe("Data") { + // MARK: -- can convert to a cArray it("can convert to a cArray") { expect(Data([1, 2, 3]).cArray).to(equal([1, 2, 3])) } + // MARK: -- when initialised with a libSession value context("when initialised with a libSession value") { + // MARK: ---- returns truncated data when given the wrong length it("returns truncated data when given the wrong length") { let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5) let result = Data(libSessionVal: value, count: 2) @@ -181,6 +202,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal(Data([1, 2]))) } + // MARK: ---- returns data when valid it("returns data when valid") { let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5) let result = Data(libSessionVal: value, count: 5) @@ -188,6 +210,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal(Data([1, 2, 3, 4, 5]))) } + // MARK: ---- returns data when all bytes are zero and nullIfEmpty is false it("returns data when all bytes are zero and nullIfEmpty is false") { let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0) let result = Data(libSessionVal: value, count: 5, nullIfEmpty: false) @@ -195,6 +218,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal(Data([0, 0, 0, 0, 0]))) } + // MARK: ---- returns null when all bytes are zero and nullIfEmpty is true it("returns null when all bytes are zero and nullIfEmpty is true") { let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0) let result = Data(libSessionVal: value, count: 5, nullIfEmpty: true) @@ -203,7 +227,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { } } + // MARK: -- when converting to a libSession value context("when converting to a libSession value") { + // MARK: ---- succeeeds with a valid value it("succeeeds with a valid value") { let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 5]).toLibSession() expect(result.0).to(equal(1)) @@ -213,6 +239,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result.4).to(equal(5)) } + // MARK: ---- truncates when too long it("truncates when too long") { let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 1, 2, 3, 4]).toLibSession() expect(result.0).to(equal(1)) @@ -222,6 +249,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result.4).to(equal(1)) } + // MARK: ---- fills with empty data when too short context("fills with empty data when too short") { let value: Data? = Data([1, 2, 3]) let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession() @@ -233,7 +261,9 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result.4).to(equal(0)) } + // MARK: ---- when optional context("when optional") { + // MARK: ------ returns null when null context("returns null when null") { let value: Data? = nil let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession() @@ -245,6 +275,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result.4).to(equal(0)) } + // MARK: ------ returns a libSession value when not null context("returns a libSession value when not null") { let value: Data? = Data([1, 2, 3, 4, 5]) let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession() @@ -259,10 +290,11 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { } } - // MARK: - Array - + // MARK: - an Array describe("an Array") { + // MARK: -- when initialised with a 2D C array context("when initialised with a 2D C array") { + // MARK: ---- returns the correct array it("returns the correct array") { var test: [CChar] = ( "Test1".cArray.nullTerminated() + @@ -278,6 +310,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal(["Test1", "Test2", "Test3AndExtra"])) } + // MARK: ---- returns an empty array if given one it("returns an empty array if given one") { var test = [CChar]() let result = test.withUnsafeMutableBufferPointer { ptr in @@ -289,6 +322,7 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal([])) } + // MARK: ---- handles empty strings without issues it("handles empty strings without issues") { var test: [CChar] = ( "Test1".cArray.nullTerminated() + @@ -304,10 +338,12 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal(["Test1", "", "Test2"])) } + // MARK: ---- returns null when given a null pointer it("returns null when given a null pointer") { expect([String](pointer: nil, count: 5)).to(beNil()) } + // MARK: ---- returns null when given a null count it("returns null when given a null count") { var test: [CChar] = "Test1".cArray.nullTerminated() let result = test.withUnsafeMutableBufferPointer { ptr in @@ -319,19 +355,23 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { expect(result).to(beNil()) } + // MARK: ---- returns the default value if given null values it("returns the default value if given null values") { expect([String](pointer: nil, count: 5, defaultValue: ["Test"])) .to(equal(["Test"])) } } + // MARK: -- when adding a null terminated character context("when adding a null terminated character") { + // MARK: ---- adds a null termination character when not present it("adds a null termination character when not present") { let value: [CChar] = [1, 2, 3, 4, 5] expect(value.nullTerminated()).to(equal([1, 2, 3, 4, 5, 0])) } + // MARK: ---- adds nothing when already present it("adds nothing when already present") { let value: [CChar] = [1, 2, 3, 4, 0] diff --git a/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift b/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift index 1d8508abe..258c2c1a9 100644 --- a/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift @@ -9,22 +9,19 @@ import Quick import Nimble @testable import SessionMessagingKit -import AVFoundation class BatchRequestInfoSpec: QuickSpec { - struct TestType: Codable, Equatable { - let stringValue: String - } - - // MARK: - Spec - - override func spec() { - // MARK: - BatchRequest.Child + override class func spec() { + // MARK: Configuration + + @TestState var request: OpenGroupAPI.BatchRequest! + + // MARK: - a BatchRequest.Child describe("a BatchRequest.Child") { - var request: OpenGroupAPI.BatchRequest! - + // MARK: -- when encoding context("when encoding") { + // MARK: ---- successfully encodes a string body it("successfully encodes a string body") { request = OpenGroupAPI.BatchRequest( requests: [ @@ -53,6 +50,7 @@ class BatchRequestInfoSpec: QuickSpec { expect(requestJson?.first?["b64"] as? String).to(equal("testBody")) } + // MARK: ---- successfully encodes a byte body it("successfully encodes a byte body") { request = OpenGroupAPI.BatchRequest( requests: [ @@ -81,6 +79,7 @@ class BatchRequestInfoSpec: QuickSpec { expect(requestJson?.first?["bytes"] as? [Int]).to(equal([1, 2, 3])) } + // MARK: ---- successfully encodes a JSON body it("successfully encodes a JSON body") { request = OpenGroupAPI.BatchRequest( requests: [ @@ -109,6 +108,7 @@ class BatchRequestInfoSpec: QuickSpec { expect(requestJson?.first?["json"] as? [String: String]).to(equal(["stringValue": "testValue"])) } + // MARK: ---- strips authentication headers it("strips authentication headers") { let httpRequest: Request = Request( method: .get, @@ -149,6 +149,7 @@ class BatchRequestInfoSpec: QuickSpec { } } + // MARK: -- does not strip non authentication headers it("does not strip non authentication headers") { let httpRequest: Request = Request( method: .get, @@ -185,3 +186,9 @@ class BatchRequestInfoSpec: QuickSpec { } } } + +// MARK: - Test Types + +fileprivate struct TestType: Codable, Equatable { + let stringValue: String +} diff --git a/SessionMessagingKitTests/Open Groups/Models/CapabilitiesSpec.swift b/SessionMessagingKitTests/Open Groups/Models/CapabilitiesSpec.swift index 220c9bad2..0fc98cf1a 100644 --- a/SessionMessagingKitTests/Open Groups/Models/CapabilitiesSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/CapabilitiesSpec.swift @@ -8,11 +8,12 @@ import Nimble @testable import SessionMessagingKit class CapabilitiesSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - Capabilities describe("Capabilities") { + // MARK: -- when initializing context("when initializing") { + // MARK: ---- assigns values correctly it("assigns values correctly") { let capabilities: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities( capabilities: [.sogs], @@ -34,8 +35,11 @@ class CapabilitiesSpec: QuickSpec { } } + // MARK: - a Capability describe("a Capability") { + // MARK: -- when initializing context("when initializing") { + // MARK: ---- succeeeds with a valid case it("succeeeds with a valid case") { let capability: Capability.Variant = Capability.Variant( from: "sogs" @@ -44,6 +48,7 @@ class CapabilitiesSpec: QuickSpec { expect(capability).to(equal(.sogs)) } + // MARK: ---- wraps an unknown value in the unsupported case it("wraps an unknown value in the unsupported case") { let capability: Capability.Variant = Capability.Variant( from: "test" @@ -53,18 +58,23 @@ class CapabilitiesSpec: QuickSpec { } } + // MARK: -- when accessing the rawValue context("when accessing the rawValue") { + // MARK: ---- provides known cases exactly it("provides known cases exactly") { expect(Capability.Variant.sogs.rawValue).to(equal("sogs")) expect(Capability.Variant.blind.rawValue).to(equal("blind")) } + // MARK: ---- provides the wrapped value for unsupported cases it("provides the wrapped value for unsupported cases") { expect(Capability.Variant.unsupported("test").rawValue).to(equal("test")) } } + // MARK: -- when Decoding context("when Decoding") { + // MARK: ---- decodes known cases exactly it("decodes known cases exactly") { expect( try? JSONDecoder().decode( @@ -82,6 +92,7 @@ class CapabilitiesSpec: QuickSpec { .to(equal(.blind)) } + // MARK: ---- decodes unknown cases into the unsupported case it("decodes unknown cases into the unsupported case") { expect( try? JSONDecoder().decode( diff --git a/SessionMessagingKitTests/Open Groups/Models/OpenGroupSpec.swift b/SessionMessagingKitTests/Open Groups/Models/OpenGroupSpec.swift index ea835122f..3ad7cae22 100644 --- a/SessionMessagingKitTests/Open Groups/Models/OpenGroupSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/OpenGroupSpec.swift @@ -8,11 +8,12 @@ import Nimble @testable import SessionMessagingKit class OpenGroupSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - an Open Group describe("an Open Group") { + // MARK: -- when initializing context("when initializing") { + // MARK: ---- generates the id it("generates the id") { let openGroup: OpenGroup = OpenGroup( server: "server", @@ -34,7 +35,9 @@ class OpenGroupSpec: QuickSpec { } } + // MARK: -- when describing context("when describing") { + // MARK: ---- includes relevant information it("includes relevant information") { let openGroup: OpenGroup = OpenGroup( server: "server", @@ -57,7 +60,9 @@ class OpenGroupSpec: QuickSpec { } } + // MARK: -- when describing in debug context("when describing in debug") { + // MARK: ---- includes relevant information it("includes relevant information") { let openGroup: OpenGroup = OpenGroup( server: "server", @@ -80,15 +85,19 @@ class OpenGroupSpec: QuickSpec { } } + // MARK: -- when generating an id context("when generating an id") { + // MARK: ---- generates correctly it("generates correctly") { expect(OpenGroup.idFor(roomToken: "room", server: "server")).to(equal("server.room")) } + // MARK: ---- converts the server to lowercase it("converts the server to lowercase") { expect(OpenGroup.idFor(roomToken: "room", server: "SeRVeR")).to(equal("server.room")) } + // MARK: ---- maintains the casing of the roomToken it("maintains the casing of the roomToken") { expect(OpenGroup.idFor(roomToken: "RoOM", server: "server")).to(equal("server.RoOM")) } diff --git a/SessionMessagingKitTests/Open Groups/Models/RoomPollInfoSpec.swift b/SessionMessagingKitTests/Open Groups/Models/RoomPollInfoSpec.swift index 4d5156ef3..386f2f02b 100644 --- a/SessionMessagingKitTests/Open Groups/Models/RoomPollInfoSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/RoomPollInfoSpec.swift @@ -8,11 +8,12 @@ import Nimble @testable import SessionMessagingKit class RoomPollInfoSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - a RoomPollInfo describe("a RoomPollInfo") { + // MARK: -- when initializing with a room context("when initializing with a room") { + // MARK: ---- copies all the relevant values across it("copies all the relevant values across") { let room: OpenGroupAPI.Room = OpenGroupAPI.Room( token: "testToken", @@ -60,7 +61,9 @@ class RoomPollInfoSpec: QuickSpec { } } + // MARK: -- when decoding context("when decoding") { + // MARK: ---- defaults admin and moderator values to false if omitted it("defaults admin and moderator values to false if omitted") { let roomPollInfoJson: String = """ { @@ -87,6 +90,7 @@ class RoomPollInfoSpec: QuickSpec { expect(result.globalModerator).to(beFalse()) } + // MARK: ---- sets the admin and moderator values when provided it("sets the admin and moderator values when provided") { let roomPollInfoJson: String = """ { diff --git a/SessionMessagingKitTests/Open Groups/Models/RoomSpec.swift b/SessionMessagingKitTests/Open Groups/Models/RoomSpec.swift index 16a3ab84b..2fd43d679 100644 --- a/SessionMessagingKitTests/Open Groups/Models/RoomSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/RoomSpec.swift @@ -8,11 +8,12 @@ import Nimble @testable import SessionMessagingKit class RoomSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - a Room describe("a Room") { + // MARK: -- when decoding context("when decoding") { + // MARK: ---- defaults admin and moderator values to false if omitted it("defaults admin and moderator values to false if omitted") { let roomJson: String = """ { @@ -52,6 +53,7 @@ class RoomSpec: QuickSpec { expect(result.globalModerator).to(beFalse()) } + // MARK: ---- sets the admin and moderator values when provided it("sets the admin and moderator values when provided") { let roomJson: String = """ { diff --git a/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift b/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift index 040785fe8..a5fbf423b 100644 --- a/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift @@ -9,44 +9,38 @@ import SessionUtilitiesKit @testable import SessionMessagingKit class SOGSMessageSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: Configuration + + @TestState var messageJson: String! = """ + { + "id": 123, + "session_id": "05\(TestConstants.publicKey)", + "posted": 234, + "seqno": 345, + "whisper": false, + "whisper_mods": false, + + "data": "VGVzdERhdGE=", + "signature": "VGVzdFNpZ25hdHVyZQ==" + } + """ + @TestState var messageData: Data! = messageJson.data(using: .utf8)! + @TestState var mockCrypto: MockCrypto! = MockCrypto() + @TestState var dependencies: Dependencies! = Dependencies( + crypto: mockCrypto + ) + @TestState var decoder: JSONDecoder! = { + let result = JSONDecoder() + result.userInfo = [ Dependencies.userInfoKey: dependencies as Any ] + return result + }() + + // MARK: - a SOGSMessage describe("a SOGSMessage") { - var messageJson: String! - var messageData: Data! - var decoder: JSONDecoder! - var mockCrypto: MockCrypto! - var dependencies: Dependencies! - - beforeEach { - messageJson = """ - { - "id": 123, - "session_id": "05\(TestConstants.publicKey)", - "posted": 234, - "seqno": 345, - "whisper": false, - "whisper_mods": false, - - "data": "VGVzdERhdGE=", - "signature": "VGVzdFNpZ25hdHVyZQ==" - } - """ - messageData = messageJson.data(using: .utf8)! - mockCrypto = MockCrypto() - dependencies = Dependencies( - crypto: mockCrypto - ) - decoder = JSONDecoder() - decoder.userInfo = [ Dependencies.userInfoKey: dependencies as Any ] - } - - afterEach { - mockCrypto = nil - } - + // MARK: -- when decoding context("when decoding") { + // MARK: ---- defaults the whisper values to false it("defaults the whisper values to false") { messageJson = """ { @@ -63,7 +57,9 @@ class SOGSMessageSpec: QuickSpec { expect(result?.whisperMods).to(beFalse()) } + // MARK: ---- and there is no content context("and there is no content") { + // MARK: ------ does not need a sender it("does not need a sender") { messageJson = """ { @@ -84,7 +80,9 @@ class SOGSMessageSpec: QuickSpec { } } + // MARK: ---- and there is content context("and there is content") { + // MARK: ------ errors if there is no sender it("errors if there is no sender") { messageJson = """ { @@ -106,6 +104,7 @@ class SOGSMessageSpec: QuickSpec { .to(throwError(HTTPError.parsingFailed)) } + // MARK: ------ errors if the data is not a base64 encoded string it("errors if the data is not a base64 encoded string") { messageJson = """ { @@ -128,6 +127,7 @@ class SOGSMessageSpec: QuickSpec { .to(throwError(HTTPError.parsingFailed)) } + // MARK: ------ errors if the signature is not a base64 encoded string it("errors if the signature is not a base64 encoded string") { messageJson = """ { @@ -150,6 +150,7 @@ class SOGSMessageSpec: QuickSpec { .to(throwError(HTTPError.parsingFailed)) } + // MARK: ------ errors if the dependencies are not provided to the JSONDecoder it("errors if the dependencies are not provided to the JSONDecoder") { decoder = JSONDecoder() @@ -159,6 +160,7 @@ class SOGSMessageSpec: QuickSpec { .to(throwError(HTTPError.parsingFailed)) } + // MARK: ------ errors if the session_id value is not valid it("errors if the session_id value is not valid") { messageJson = """ { @@ -181,7 +183,7 @@ class SOGSMessageSpec: QuickSpec { .to(throwError(HTTPError.parsingFailed)) } - + // MARK: ------ that is blinded context("that is blinded") { beforeEach { messageJson = """ @@ -200,6 +202,7 @@ class SOGSMessageSpec: QuickSpec { messageData = messageJson.data(using: .utf8)! } + // MARK: -------- succeeds if it succeeds verification it("succeeds if it succeeds verification") { mockCrypto .when { @@ -213,6 +216,7 @@ class SOGSMessageSpec: QuickSpec { .toNot(beNil()) } + // MARK: -------- provides the correct values as parameters it("provides the correct values as parameters") { mockCrypto .when { @@ -234,6 +238,7 @@ class SOGSMessageSpec: QuickSpec { }) } + // MARK: -------- throws if it fails verification it("throws if it fails verification") { mockCrypto .when { @@ -248,7 +253,9 @@ class SOGSMessageSpec: QuickSpec { } } + // MARK: ------ that is unblinded context("that is unblinded") { + // MARK: -------- succeeds if it succeeds verification it("succeeds if it succeeds verification") { mockCrypto .when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) } @@ -260,6 +267,7 @@ class SOGSMessageSpec: QuickSpec { .toNot(beNil()) } + // MARK: -------- provides the correct values as parameters it("provides the correct values as parameters") { mockCrypto .when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) } @@ -279,6 +287,7 @@ class SOGSMessageSpec: QuickSpec { }) } + // MARK: -------- throws if it fails verification it("throws if it fails verification") { mockCrypto .when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) } diff --git a/SessionMessagingKitTests/Open Groups/Models/SendDirectMessageRequestSpec.swift b/SessionMessagingKitTests/Open Groups/Models/SendDirectMessageRequestSpec.swift index 228176a15..27e96dd20 100644 --- a/SessionMessagingKitTests/Open Groups/Models/SendDirectMessageRequestSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/SendDirectMessageRequestSpec.swift @@ -8,11 +8,12 @@ import Nimble @testable import SessionMessagingKit class SendDirectMessageRequestSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - a SendDirectMessageRequest describe("a SendDirectMessageRequest") { + // MARK: -- when encoding context("when encoding") { + // MARK: ---- encodes the data as a base64 string it("encodes the data as a base64 string") { let request: OpenGroupAPI.SendDirectMessageRequest = OpenGroupAPI.SendDirectMessageRequest( message: "TestData".data(using: .utf8)! diff --git a/SessionMessagingKitTests/Open Groups/Models/SendMessageRequestSpec.swift b/SessionMessagingKitTests/Open Groups/Models/SendMessageRequestSpec.swift index 7fd3554a3..ec0c15d38 100644 --- a/SessionMessagingKitTests/Open Groups/Models/SendMessageRequestSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/SendMessageRequestSpec.swift @@ -8,11 +8,12 @@ import Nimble @testable import SessionMessagingKit class SendMessageRequestSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - a SendMessageRequest describe("a SendMessageRequest") { + // MARK: -- when initializing context("when initializing") { + // MARK: ---- defaults the optional values to nil it("defaults the optional values to nil") { let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest( data: "TestData".data(using: .utf8)!, @@ -25,7 +26,9 @@ class SendMessageRequestSpec: QuickSpec { } } + // MARK: -- when encoding context("when encoding") { + // MARK: ---- encodes the data as a base64 string it("encodes the data as a base64 string") { let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest( data: "TestData".data(using: .utf8)!, @@ -41,6 +44,7 @@ class SendMessageRequestSpec: QuickSpec { expect(requestDataString).to(contain("VGVzdERhdGE=")) } + // MARK: ---- encodes the signature as a base64 string it("encodes the signature as a base64 string") { let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest( data: "TestData".data(using: .utf8)!, diff --git a/SessionMessagingKitTests/Open Groups/Models/UpdateMessageRequestSpec.swift b/SessionMessagingKitTests/Open Groups/Models/UpdateMessageRequestSpec.swift index f63b2e16c..106bd04c5 100644 --- a/SessionMessagingKitTests/Open Groups/Models/UpdateMessageRequestSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/UpdateMessageRequestSpec.swift @@ -8,11 +8,12 @@ import Nimble @testable import SessionMessagingKit class UpdateMessageRequestSpec: QuickSpec { - // MARK: - Spec - - override func spec() { - describe("a UpdateMessageRequest") { + override class func spec() { + // MARK: - an UpdateMessageRequest + describe("an UpdateMessageRequest") { + // MARK: -- when encoding context("when encoding") { + // MARK: ---- encodes the data as a base64 string it("encodes the data as a base64 string") { let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest( data: "TestData".data(using: .utf8)!, @@ -26,6 +27,7 @@ class UpdateMessageRequestSpec: QuickSpec { expect(requestDataString).to(contain("VGVzdERhdGE=")) } + // MARK: ---- encodes the signature as a base64 string it("encodes the signature as a base64 string") { let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest( data: "TestData".data(using: .utf8)!, diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift index 8586c99d1..0470715e5 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift @@ -13,66 +13,49 @@ import Nimble @testable import SessionMessagingKit class OpenGroupAPISpec: QuickSpec { - // MARK: - Spec - - override func spec() { - var mockStorage: Storage! - var mockNetwork: MockNetwork! - var mockCrypto: MockCrypto! - var dependencies: Dependencies! - var disposables: [AnyCancellable] = [] + override class func spec() { + // MARK: Configuration - var error: Error? - - describe("an OpenGroupAPI") { - // MARK: - Configuration - - beforeEach { - mockStorage = SynchronousStorage( - customWriter: try! DatabaseQueue(), - customMigrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ] - ) - mockNetwork = MockNetwork() - mockCrypto = MockCrypto() - dependencies = Dependencies( - storage: mockStorage, - network: mockNetwork, - crypto: mockCrypto, - dateNow: Date(timeIntervalSince1970: 1234567890) - ) + @TestState var mockStorage: Storage! = SynchronousStorage( + customWriter: try! DatabaseQueue(), + customMigrationTargets: [ + SNUtilitiesKit.self, + SNMessagingKit.self + ], + initialData: { db in + try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).insert(db) + try Identity(variant: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).insert(db) + try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: TestConstants.edPublicKey)!).insert(db) + try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).insert(db) - mockStorage.write { db in - try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).insert(db) - try Identity(variant: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).insert(db) - try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: TestConstants.edPublicKey)!).insert(db) - try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).insert(db) - - try OpenGroup( - server: "testServer", - roomToken: "testRoom", - publicKey: TestConstants.publicKey, - isActive: true, - name: "Test", - roomDescription: nil, - imageId: nil, - userCount: 0, - infoUpdates: 0, - sequenceNumber: 0, - inboxLatestMessageId: 0, - outboxLatestMessageId: 0 - ).insert(db) - try Capability(openGroupServer: "testserver", variant: .sogs, isMissing: false).insert(db) - } - - mockCrypto + try OpenGroup( + server: "testServer", + roomToken: "testRoom", + publicKey: TestConstants.publicKey, + isActive: true, + name: "Test", + roomDescription: nil, + imageId: nil, + userCount: 0, + infoUpdates: 0, + sequenceNumber: 0, + inboxLatestMessageId: 0, + outboxLatestMessageId: 0 + ).insert(db) + try Capability(openGroupServer: "testserver", variant: .sogs, isMissing: false).insert(db) + } + ) + @TestState var mockNetwork: MockNetwork! = MockNetwork() + @TestState var mockCrypto: MockCrypto! = MockCrypto( + initialSetup: { crypto in + crypto .when { try $0.perform(.hash(message: anyArray(), outputLength: any())) } .thenReturn([]) - mockCrypto - .when { [dependencies = dependencies!] crypto in - crypto.generate(.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), using: dependencies)) + crypto + .when { crypto in + crypto.generate( + .blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), using: any()) + ) } .thenReturn( KeyPair( @@ -80,7 +63,7 @@ class OpenGroupAPISpec: QuickSpec { secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ) ) - mockCrypto + crypto .when { try $0.perform( .sogsSignature( @@ -92,35 +75,34 @@ class OpenGroupAPISpec: QuickSpec { ) } .thenReturn("TestSogsSignature".bytes) - mockCrypto + crypto .when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) } .thenReturn("TestSignature".bytes) - mockCrypto + crypto .when { try $0.perform(.signEd25519(data: anyArray(), keyPair: any())) } .thenReturn("TestStandardSignature".bytes) - mockCrypto + crypto .when { try $0.perform(.generateNonce16()) } .thenReturn(Data(base64Encoded: "pK6YRtQApl4NhECGizF0Cg==")!.bytes) - mockCrypto + crypto .when { try $0.perform(.generateNonce24()) } .thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes) } - - afterEach { - disposables.forEach { $0.cancel() } - - mockStorage = nil - mockNetwork = nil - mockCrypto = nil - dependencies = nil - disposables = [] - - error = nil - } - - // MARK: - when preparing a poll request + ) + @TestState var dependencies: Dependencies! = Dependencies( + storage: mockStorage, + network: mockNetwork, + crypto: mockCrypto, + dateNow: Date(timeIntervalSince1970: 1234567890) + ) + @TestState var disposables: [AnyCancellable]! = [] + @TestState var error: Error? + + // MARK: - an OpenGroupAPI + describe("an OpenGroupAPI") { + // MARK: -- when preparing a poll request context("when preparing a poll request") { - // MARK: -- generates the correct request + // MARK: ---- generates the correct request it("generates the correct request") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedPoll( @@ -140,7 +122,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.batchEndpoints[test: 2]).to(equal(.roomMessagesRecent("testRoom"))) } - // MARK: -- retrieves recent messages if there was no last message + // MARK: ---- retrieves recent messages if there was no last message it("retrieves recent messages if there was no last message") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedPoll( @@ -155,7 +137,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.batchEndpoints[test: 2]).to(equal(.roomMessagesRecent("testRoom"))) } - // MARK: -- retrieves recent messages if there was a last message and it has not performed the initial poll and the last message was too long ago + // MARK: ---- retrieves recent messages if there was a last message and it has not performed the initial poll and the last message was too long ago it("retrieves recent messages if there was a last message and it has not performed the initial poll and the last message was too long ago") { mockStorage.write { db in try OpenGroup @@ -175,7 +157,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.batchEndpoints[test: 2]).to(equal(.roomMessagesRecent("testRoom"))) } - // MARK: -- retrieves recent messages if there was a last message and it has performed an initial poll but it was not too long ago + // MARK: ---- retrieves recent messages if there was a last message and it has performed an initial poll but it was not too long ago it("retrieves recent messages if there was a last message and it has performed an initial poll but it was not too long ago") { mockStorage.write { db in try OpenGroup @@ -196,7 +178,7 @@ class OpenGroupAPISpec: QuickSpec { .to(equal(.roomMessagesSince("testRoom", seqNo: 122))) } - // MARK: -- retrieves recent messages if there was a last message and there has already been a poll this session + // MARK: ---- retrieves recent messages if there was a last message and there has already been a poll this session it("retrieves recent messages if there was a last message and there has already been a poll this session") { mockStorage.write { db in try OpenGroup @@ -217,7 +199,7 @@ class OpenGroupAPISpec: QuickSpec { .to(equal(.roomMessagesSince("testRoom", seqNo: 123))) } - // MARK: -- when unblinded + // MARK: ---- when unblinded context("when unblinded") { beforeEach { mockStorage.write { db in @@ -226,7 +208,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: ---- does not call the inbox and outbox endpoints + // MARK: ------ does not call the inbox and outbox endpoints it("does not call the inbox and outbox endpoints") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedPoll( @@ -243,7 +225,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: -- when blinded and checking for message requests + // MARK: ---- when blinded and checking for message requests context("when blinded and checking for message requests") { beforeEach { mockStorage.write { db in @@ -255,7 +237,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: ---- includes the inbox and outbox endpoints + // MARK: ------ includes the inbox and outbox endpoints it("includes the inbox and outbox endpoints") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedPoll( @@ -271,7 +253,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.batchEndpoints).to(contain(.outbox)) } - // MARK: ---- retrieves recent inbox messages if there was no last message + // MARK: ------ retrieves recent inbox messages if there was no last message it("retrieves recent inbox messages if there was no last message") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedPoll( @@ -286,7 +268,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.batchEndpoints).to(contain(.inbox)) } - // MARK: ---- retrieves inbox messages since the last message if there was one + // MARK: ------ retrieves inbox messages since the last message if there was one it("retrieves inbox messages since the last message if there was one") { mockStorage.write { db in try OpenGroup @@ -306,7 +288,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.batchEndpoints).to(contain(.inboxSince(id: 124))) } - // MARK: ---- retrieves recent outbox messages if there was no last message + // MARK: ------ retrieves recent outbox messages if there was no last message it("retrieves recent outbox messages if there was no last message") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedPoll( @@ -321,7 +303,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.batchEndpoints).to(contain(.outbox)) } - // MARK: ---- retrieves outbox messages since the last message if there was one + // MARK: ------ retrieves outbox messages since the last message if there was one it("retrieves outbox messages since the last message if there was one") { mockStorage.write { db in try OpenGroup @@ -342,7 +324,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: -- when blinded and not checking for message requests + // MARK: ---- when blinded and not checking for message requests context("when blinded and not checking for message requests") { beforeEach { mockStorage.write { db in @@ -354,7 +336,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: ---- includes the inbox and outbox endpoints + // MARK: ------ includes the inbox and outbox endpoints it("does not include the inbox endpoint") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedPoll( @@ -369,7 +351,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.batchEndpoints).toNot(contain(.inbox)) } - // MARK: ---- does not retrieve recent inbox messages if there was no last message + // MARK: ------ does not retrieve recent inbox messages if there was no last message it("does not retrieve recent inbox messages if there was no last message") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedPoll( @@ -384,7 +366,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.batchEndpoints).toNot(contain(.inbox)) } - // MARK: ---- does not retrieve inbox messages since the last message if there was one + // MARK: ------ does not retrieve inbox messages since the last message if there was one it("does not retrieve inbox messages since the last message if there was one") { mockStorage.write { db in try OpenGroup @@ -406,9 +388,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing a capabilities request + // MARK: -- when preparing a capabilities request context("when preparing a capabilities request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request and handles the response correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedCapabilities( @@ -423,10 +405,10 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing a rooms request + // MARK: -- when preparing a rooms request context("when preparing a rooms request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData<[OpenGroupAPI.Room]>? = mockStorage.read { db in try OpenGroupAPI.preparedRooms( @@ -441,9 +423,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing a capabilitiesAndRoom request + // MARK: -- when preparing a capabilitiesAndRoom request context("when preparing a capabilitiesAndRoom request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedCapabilitiesAndRoom( @@ -462,7 +444,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.request.httpMethod).to(equal("POST")) } - // MARK: -- processes a valid response correctly + // MARK: ---- processes a valid response correctly it("processes a valid response correctly") { mockNetwork .when { $0.send(.onionRequest(any(), to: any(), with: any())) } @@ -488,10 +470,10 @@ class OpenGroupAPISpec: QuickSpec { expect(error).to(beNil()) } - // MARK: -- and given an invalid response + // MARK: ---- and given an invalid response context("and given an invalid response") { - // MARK: ---- errors when not given a room response + // MARK: ------ errors when not given a room response it("errors when not given a room response") { mockNetwork .when { $0.send(.onionRequest(any(), to: any(), with: any())) } @@ -517,7 +499,7 @@ class OpenGroupAPISpec: QuickSpec { expect(response).to(beNil()) } - // MARK: ---- errors when not given a capabilities response + // MARK: ------ errors when not given a capabilities response it("errors when not given a capabilities response") { mockNetwork .when { $0.send(.onionRequest(any(), to: any(), with: any())) } @@ -545,9 +527,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing a capabilitiesAndRooms request + // MARK: -- when preparing a capabilitiesAndRooms request context("when preparing a capabilitiesAndRooms request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedCapabilitiesAndRooms( @@ -565,7 +547,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.request.httpMethod).to(equal("POST")) } - // MARK: -- processes a valid response correctly + // MARK: ---- processes a valid response correctly it("processes a valid response correctly") { mockNetwork .when { $0.send(.onionRequest(any(), to: any(), with: any())) } @@ -590,10 +572,10 @@ class OpenGroupAPISpec: QuickSpec { expect(error).to(beNil()) } - // MARK: -- and given an invalid response + // MARK: ---- and given an invalid response context("and given an invalid response") { - // MARK: ---- errors when not given a room response + // MARK: ------ errors when not given a room response it("errors when not given a room response") { mockNetwork .when { $0.send(.onionRequest(any(), to: any(), with: any())) } @@ -618,7 +600,7 @@ class OpenGroupAPISpec: QuickSpec { expect(response).to(beNil()) } - // MARK: ---- errors when not given a capabilities response + // MARK: ------ errors when not given a capabilities response it("errors when not given a capabilities response") { mockNetwork .when { $0.send(.onionRequest(any(), to: any(), with: any())) } @@ -645,9 +627,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing a send message request + // MARK: -- when preparing a send message request context("when preparing a send message request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedSend( @@ -666,7 +648,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.request.httpMethod).to(equal("POST")) } - // MARK: -- when unblinded + // MARK: ---- when unblinded context("when unblinded") { beforeEach { mockStorage.write { db in @@ -675,7 +657,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: ---- signs the message correctly + // MARK: ------ signs the message correctly it("signs the message correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedSend( @@ -696,7 +678,7 @@ class OpenGroupAPISpec: QuickSpec { expect(requestBody?.signature).to(equal("TestStandardSignature".data(using: .utf8))) } - // MARK: ---- fails to sign if there is no open group + // MARK: ------ fails to sign if there is no open group it("fails to sign if there is no open group") { mockStorage.write { db in _ = try OpenGroup.deleteAll(db) @@ -726,7 +708,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest).to(beNil()) } - // MARK: ---- fails to sign if there is no user key pair + // MARK: ------ fails to sign if there is no user key pair it("fails to sign if there is no user key pair") { mockStorage.write { db in _ = try Identity.filter(id: .x25519PublicKey).deleteAll(db) @@ -757,7 +739,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest).to(beNil()) } - // MARK: ---- fails to sign if no signature is generated + // MARK: ------ fails to sign if no signature is generated it("fails to sign if no signature is generated") { mockCrypto.reset() // The 'keyPair' value doesn't equate so have to explicitly reset mockCrypto @@ -789,7 +771,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: -- when blinded + // MARK: ---- when blinded context("when blinded") { beforeEach { mockStorage.write { db in @@ -799,7 +781,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: ---- signs the message correctly + // MARK: ------ signs the message correctly it("signs the message correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedSend( @@ -820,7 +802,7 @@ class OpenGroupAPISpec: QuickSpec { expect(requestBody?.signature).to(equal("TestSogsSignature".data(using: .utf8))) } - // MARK: ---- fails to sign if there is no open group + // MARK: ------ fails to sign if there is no open group it("fails to sign if there is no open group") { mockStorage.write { db in _ = try OpenGroup.deleteAll(db) @@ -850,7 +832,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest).to(beNil()) } - // MARK: ---- fails to sign if there is no ed key pair key + // MARK: ------ fails to sign if there is no ed key pair key it("fails to sign if there is no ed key pair key") { mockStorage.write { db in _ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db) @@ -881,7 +863,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest).to(beNil()) } - // MARK: ---- fails to sign if no signature is generated + // MARK: ------ fails to sign if no signature is generated it("fails to sign if no signature is generated") { mockCrypto .when { @@ -922,9 +904,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing an individual message request + // MARK: -- when preparing an individual message request context("when preparing an individual message request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedMessage( @@ -941,7 +923,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing an update message request + // MARK: -- when preparing an update message request context("when preparing an update message request") { beforeEach { mockStorage.write { db in @@ -954,7 +936,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedMessageUpdate( @@ -972,7 +954,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.request.httpMethod).to(equal("PUT")) } - // MARK: -- when unblinded + // MARK: ---- when unblinded context("when unblinded") { beforeEach { mockStorage.write { db in @@ -981,7 +963,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: ---- signs the message correctly + // MARK: ------ signs the message correctly it("signs the message correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedMessageUpdate( @@ -1001,7 +983,7 @@ class OpenGroupAPISpec: QuickSpec { expect(requestBody?.signature).to(equal("TestStandardSignature".data(using: .utf8))) } - // MARK: ---- fails to sign if there is no open group + // MARK: ------ fails to sign if there is no open group it("fails to sign if there is no open group") { mockStorage.write { db in _ = try OpenGroup.deleteAll(db) @@ -1030,7 +1012,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest).to(beNil()) } - // MARK: ---- fails to sign if there is no user key pair + // MARK: ------ fails to sign if there is no user key pair it("fails to sign if there is no user key pair") { mockStorage.write { db in _ = try Identity.filter(id: .x25519PublicKey).deleteAll(db) @@ -1060,7 +1042,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest).to(beNil()) } - // MARK: ---- fails to sign if no signature is generated + // MARK: ------ fails to sign if no signature is generated it("fails to sign if no signature is generated") { mockCrypto.reset() // The 'keyPair' value doesn't equate so have to explicitly reset mockCrypto @@ -1091,7 +1073,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: -- when blinded + // MARK: ---- when blinded context("when blinded") { beforeEach { mockStorage.write { db in @@ -1101,7 +1083,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: ---- signs the message correctly + // MARK: ------ signs the message correctly it("signs the message correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedMessageUpdate( @@ -1121,7 +1103,7 @@ class OpenGroupAPISpec: QuickSpec { expect(requestBody?.signature).to(equal("TestSogsSignature".data(using: .utf8))) } - // MARK: ---- fails to sign if there is no open group + // MARK: ------ fails to sign if there is no open group it("fails to sign if there is no open group") { mockStorage.write { db in _ = try OpenGroup.deleteAll(db) @@ -1150,7 +1132,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest).to(beNil()) } - // MARK: ---- fails to sign if there is no ed key pair key + // MARK: ------ fails to sign if there is no ed key pair key it("fails to sign if there is no ed key pair key") { mockStorage.write { db in _ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db) @@ -1180,7 +1162,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest).to(beNil()) } - // MARK: ---- fails to sign if no signature is generated + // MARK: ------ fails to sign if no signature is generated it("fails to sign if no signature is generated") { mockCrypto .when { @@ -1220,9 +1202,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing a delete message request + // MARK: -- when preparing a delete message request context("when preparing a delete message request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedMessageDelete( @@ -1239,9 +1221,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing a delete all messages request + // MARK: -- when preparing a delete all messages request context("when preparing a delete all messages request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedMessagesDeleteAll( @@ -1258,9 +1240,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing a pin message request + // MARK: -- when preparing a pin message request context("when preparing a pin message request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedPinMessage( @@ -1277,9 +1259,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing an unpin message request + // MARK: -- when preparing an unpin message request context("when preparing an unpin message request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUnpinMessage( @@ -1296,9 +1278,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing an unpin all request + // MARK: -- when preparing an unpin all request context("when preparing an unpin all request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUnpinAll( @@ -1314,9 +1296,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing an upload file request + // MARK: -- when preparing an upload file request context("when preparing an upload file request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUploadFile( @@ -1332,7 +1314,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.request.httpMethod).to(equal("POST")) } - // MARK: -- doesn't add a fileName to the content-disposition header when not provided + // MARK: ---- doesn't add a fileName to the content-disposition header when not provided it("doesn't add a fileName to the content-disposition header when not provided") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUploadFile( @@ -1348,7 +1330,7 @@ class OpenGroupAPISpec: QuickSpec { .toNot(contain("filename")) } - // MARK: -- adds the fileName to the content-disposition header when provided + // MARK: ---- adds the fileName to the content-disposition header when provided it("adds the fileName to the content-disposition header when provided") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUploadFile( @@ -1366,9 +1348,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing a download file request + // MARK: -- when preparing a download file request context("when preparing a download file request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedDownloadFile( @@ -1385,9 +1367,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing a send direct message request + // MARK: -- when preparing a send direct message request context("when preparing a send direct message request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedSend( @@ -1404,9 +1386,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing a ban user request + // MARK: -- when preparing a ban user request context("when preparing a ban user request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUserBan( @@ -1423,7 +1405,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.request.httpMethod).to(equal("POST")) } - // MARK: -- does a global ban if no room tokens are provided + // MARK: ---- does a global ban if no room tokens are provided it("does a global ban if no room tokens are provided") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUserBan( @@ -1442,7 +1424,7 @@ class OpenGroupAPISpec: QuickSpec { expect(requestBody?.rooms).to(beNil()) } - // MARK: -- does room specific bans if room tokens are provided + // MARK: ---- does room specific bans if room tokens are provided it("does room specific bans if room tokens are provided") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUserBan( @@ -1462,9 +1444,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing an unban user request + // MARK: -- when preparing an unban user request context("when preparing an unban user request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUserUnban( @@ -1480,7 +1462,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.request.httpMethod).to(equal("POST")) } - // MARK: -- does a global unban if no room tokens are provided + // MARK: ---- does a global unban if no room tokens are provided it("does a global unban if no room tokens are provided") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUserUnban( @@ -1498,7 +1480,7 @@ class OpenGroupAPISpec: QuickSpec { expect(requestBody?.rooms).to(beNil()) } - // MARK: -- does room specific unbans if room tokens are provided + // MARK: ---- does room specific unbans if room tokens are provided it("does room specific unbans if room tokens are provided") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUserUnban( @@ -1517,9 +1499,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing a user permissions request + // MARK: -- when preparing a user permissions request context("when preparing a user permissions request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUserModeratorUpdate( @@ -1538,7 +1520,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.request.httpMethod).to(equal("POST")) } - // MARK: -- does a global update if no room tokens are provided + // MARK: ---- does a global update if no room tokens are provided it("does a global update if no room tokens are provided") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUserModeratorUpdate( @@ -1559,7 +1541,7 @@ class OpenGroupAPISpec: QuickSpec { expect(requestBody?.rooms).to(beNil()) } - // MARK: -- does room specific updates if room tokens are provided + // MARK: ---- does room specific updates if room tokens are provided it("does room specific updates if room tokens are provided") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUserModeratorUpdate( @@ -1580,7 +1562,7 @@ class OpenGroupAPISpec: QuickSpec { expect(requestBody?.rooms).to(equal(["testRoom"])) } - // MARK: -- fails if neither moderator or admin are set + // MARK: ---- fails if neither moderator or admin are set it("fails if neither moderator or admin are set") { var preparationError: Error? let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in @@ -1607,9 +1589,9 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: - when preparing a ban and delete all request + // MARK: -- when preparing a ban and delete all request context("when preparing a ban and delete all request") { - // MARK: -- generates the request correctly + // MARK: ---- generates the request correctly it("generates the request correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in try OpenGroupAPI.preparedUserBanAndDeleteAllMessages( @@ -1629,7 +1611,7 @@ class OpenGroupAPISpec: QuickSpec { .to(equal(.roomDeleteMessages("testRoom", sessionId: "testUserId"))) } -// // MARK: -- bans the user from the specified room rather than globally +// // MARK: ---- bans the user from the specified room rather than globally // it("bans the user from the specified room rather than globally") { // let preparedRequest: OpenGroupAPI.PreparedSendData? = mockStorage.read { db in // try OpenGroupAPI.preparedUserBanAndDeleteAllMessages( @@ -1648,9 +1630,9 @@ class OpenGroupAPISpec: QuickSpec { // } } - // MARK: - when signing + // MARK: -- when signing context("when signing") { - // MARK: -- fails when there is no serverPublicKey + // MARK: ---- fails when there is no serverPublicKey it("fails when there is no serverPublicKey") { mockStorage.write { db in _ = try OpenGroup.deleteAll(db) @@ -1675,7 +1657,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest).to(beNil()) } - // MARK: -- fails when there is no userEdKeyPair + // MARK: ---- fails when there is no userEdKeyPair it("fails when there is no userEdKeyPair") { mockStorage.write { db in _ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db) @@ -1701,7 +1683,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest).to(beNil()) } - // MARK: -- fails when the serverPublicKey is not a hex string + // MARK: ---- fails when the serverPublicKey is not a hex string it("fails when the serverPublicKey is not a hex string") { mockStorage.write { db in _ = try OpenGroup.updateAll(db, OpenGroup.Columns.publicKey.set(to: "TestString!!!")) @@ -1726,7 +1708,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest).to(beNil()) } - // MARK: -- when unblinded + // MARK: ---- when unblinded context("when unblinded") { beforeEach { mockStorage.write { db in @@ -1735,7 +1717,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: ---- signs correctly + // MARK: ------ signs correctly it("signs correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData<[OpenGroupAPI.Room]>? = mockStorage.read { db in try OpenGroupAPI.preparedRooms( @@ -1759,7 +1741,7 @@ class OpenGroupAPISpec: QuickSpec { .to(equal("TestSignature".bytes.toBase64())) } - // MARK: ---- fails when the signature is not generated + // MARK: ------ fails when the signature is not generated it("fails when the signature is not generated") { mockCrypto .when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) } @@ -1785,7 +1767,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: -- when blinded + // MARK: ---- when blinded context("when blinded") { beforeEach { mockStorage.write { db in @@ -1795,7 +1777,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: ---- signs correctly + // MARK: ------ signs correctly it("signs correctly") { let preparedRequest: OpenGroupAPI.PreparedSendData<[OpenGroupAPI.Room]>? = mockStorage.read { db in try OpenGroupAPI.preparedRooms( @@ -1819,7 +1801,7 @@ class OpenGroupAPISpec: QuickSpec { .to(equal("TestSogsSignature".bytes.toBase64())) } - // MARK: ---- fails when the blindedKeyPair is not generated + // MARK: ------ fails when the blindedKeyPair is not generated it("fails when the blindedKeyPair is not generated") { mockCrypto .when { [dependencies = dependencies!] in @@ -1852,7 +1834,7 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest).to(beNil()) } - // MARK: ---- fails when the sogsSignature is not generated + // MARK: ------ fails when the sogsSignature is not generated it("fails when the sogsSignature is not generated") { mockCrypto .when { [dependencies = dependencies!] in @@ -1887,7 +1869,7 @@ class OpenGroupAPISpec: QuickSpec { } } - // MARK: -- when sending + // MARK: ---- when sending context("when sending") { beforeEach { mockNetwork @@ -1895,7 +1877,7 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(MockNetwork.response(type: [OpenGroupAPI.Room].self)) } - // MARK: -- triggers sending correctly + // MARK: ---- triggers sending correctly it("triggers sending correctly") { var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])? @@ -1916,7 +1898,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error).to(beNil()) } - // MARK: -- fails when not given prepared data + // MARK: ---- fails when not given prepared data it("fails when not given prepared data") { var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])? diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index 46ada277e..9219e202d 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -13,158 +13,119 @@ import Nimble @testable import SessionMessagingKit class OpenGroupManagerSpec: QuickSpec { - // MARK: - Spec - - override func spec() { - var mockStorage: Storage! - var mockNetwork: MockNetwork! - var mockCrypto: MockCrypto! - var mockUserDefaults: MockUserDefaults! - var mockCaches: MockCaches! - var mockGeneralCache: MockGeneralCache! - var mockOGMCache: MockOGMCache! - var dependencies: Dependencies! - var disposables: [AnyCancellable] = [] + override class func spec() { + // MARK: Configuration - var testInteraction1: Interaction! - var testGroupThread: SessionThread! - var testOpenGroup: OpenGroup! - var testPollInfo: OpenGroupAPI.RoomPollInfo! - var testMessage: OpenGroupAPI.Message! - var testDirectMessage: OpenGroupAPI.DirectMessage! + @TestState var testInteraction1: Interaction! = Interaction( + id: 234, + serverHash: "TestServerHash", + messageUuid: nil, + threadId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), + authorId: "TestAuthorId", + variant: .standardOutgoing, + body: "Test", + timestampMs: 123, + receivedAtTimestampMs: 124, + wasRead: false, + hasMention: false, + expiresInSeconds: nil, + expiresStartedAtMs: nil, + linkPreviewUrl: nil, + openGroupServerMessageId: nil, + openGroupWhisperMods: false, + openGroupWhisperTo: nil + ) + @TestState var testGroupThread: SessionThread! = SessionThread( + id: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), + variant: .community + ) + @TestState var testOpenGroup: OpenGroup! = OpenGroup( + server: "testServer", + roomToken: "testRoom", + publicKey: TestConstants.publicKey, + isActive: true, + name: "Test", + roomDescription: nil, + imageId: nil, + imageData: nil, + userCount: 0, + infoUpdates: 10, + sequenceNumber: 5 + ) + @TestState var testPollInfo: OpenGroupAPI.RoomPollInfo! = OpenGroupAPI.RoomPollInfo.mockValue.with( + token: "testRoom", + activeUsers: 10, + details: .mockValue + ) + @TestState var testMessage: OpenGroupAPI.Message! = OpenGroupAPI.Message( + id: 127, + sender: "05\(TestConstants.publicKey)", + posted: 123, + edited: nil, + deleted: nil, + seqNo: 124, + whisper: false, + whisperMods: false, + whisperTo: nil, + base64EncodedData: [ + "Cg0KC1Rlc3RNZXNzYWdlg", + "AAAAAAAAAAAAAAAAAAAAA", + "AAAAAAAAAAAAAAAAAAAAA", + "AAAAAAAAAAAAAAAAAAAAA", + "AAAAAAAAAAAAAAAAAAAAA", + "AAAAAAAAAAAAAAAAAAAAA", + "AAAAAAAAAAAAAAAAAAAAA", + "AAAAAAAAAAAAAAAAAAAAA", + "AAAAAAAAAAAAAAAAAAAAA", + "AAAAAAAAAAAAAAAAAAAAA", + "AA" + ].joined(), + base64EncodedSignature: nil, + reactions: nil + ) + @TestState var testDirectMessage: OpenGroupAPI.DirectMessage! = OpenGroupAPI.DirectMessage( + id: 128, + sender: "15\(TestConstants.publicKey)", + recipient: "15\(TestConstants.publicKey)", + posted: 1234567890, + expires: 1234567990, + base64EncodedMessage: Data( + Bytes(arrayLiteral: 0) + + "TestMessage".bytes + + Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes + ).base64EncodedString() + ) - var cache: OpenGroupManager.Cache! - var openGroupManager: OpenGroupManager! - - describe("an OpenGroupManager") { - // MARK: - Configuration - - beforeEach { - mockStorage = SynchronousStorage( - customWriter: try! DatabaseQueue(), - customMigrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ] - ) - mockNetwork = MockNetwork() - mockCrypto = MockCrypto() - mockUserDefaults = MockUserDefaults() - mockCaches = MockCaches() - mockGeneralCache = MockGeneralCache() - mockOGMCache = MockOGMCache() - dependencies = Dependencies( - storage: mockStorage, - network: mockNetwork, - crypto: mockCrypto, - standardUserDefaults: mockUserDefaults, - caches: mockCaches, - dateNow: Date(timeIntervalSince1970: 1234567890), - forceSynchronous: true - ) - mockCaches[.general] = mockGeneralCache - mockCaches[.openGroupManager] = mockOGMCache - testInteraction1 = Interaction( - id: 234, - serverHash: "TestServerHash", - messageUuid: nil, - threadId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), - authorId: "TestAuthorId", - variant: .standardOutgoing, - body: "Test", - timestampMs: 123, - receivedAtTimestampMs: 124, - wasRead: false, - hasMention: false, - expiresInSeconds: nil, - expiresStartedAtMs: nil, - linkPreviewUrl: nil, - openGroupServerMessageId: nil, - openGroupWhisperMods: false, - openGroupWhisperTo: nil - ) + @TestState var mockStorage: Storage! = SynchronousStorage( + customWriter: try! DatabaseQueue(), + customMigrationTargets: [ + SNUtilitiesKit.self, + SNMessagingKit.self + ], + initialData: { db in + try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).insert(db) + try Identity(variant: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).insert(db) + try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: TestConstants.edPublicKey)!).insert(db) + try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).insert(db) - testGroupThread = SessionThread( - id: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), - variant: .community - ) - testOpenGroup = OpenGroup( - server: "testServer", - roomToken: "testRoom", - publicKey: TestConstants.publicKey, - isActive: true, - name: "Test", - roomDescription: nil, - imageId: nil, - imageData: nil, - userCount: 0, - infoUpdates: 10, - sequenceNumber: 5 - ) - testPollInfo = OpenGroupAPI.RoomPollInfo.mockValue.with( - token: "testRoom", - activeUsers: 10, - details: .mockValue - ) - testMessage = OpenGroupAPI.Message( - id: 127, - sender: "05\(TestConstants.publicKey)", - posted: 123, - edited: nil, - deleted: nil, - seqNo: 124, - whisper: false, - whisperMods: false, - whisperTo: nil, - base64EncodedData: [ - "Cg0KC1Rlc3RNZXNzYWdlg", - "AAAAAAAAAAAAAAAAAAAAA", - "AAAAAAAAAAAAAAAAAAAAA", - "AAAAAAAAAAAAAAAAAAAAA", - "AAAAAAAAAAAAAAAAAAAAA", - "AAAAAAAAAAAAAAAAAAAAA", - "AAAAAAAAAAAAAAAAAAAAA", - "AAAAAAAAAAAAAAAAAAAAA", - "AAAAAAAAAAAAAAAAAAAAA", - "AAAAAAAAAAAAAAAAAAAAA", - "AA" - ].joined(), - base64EncodedSignature: nil, - reactions: nil - ) - testDirectMessage = OpenGroupAPI.DirectMessage( - id: 128, - sender: "15\(TestConstants.publicKey)", - recipient: "15\(TestConstants.publicKey)", - posted: 1234567890, - expires: 1234567990, - base64EncodedMessage: Data( - Bytes(arrayLiteral: 0) + - "TestMessage".bytes + - Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes - ).base64EncodedString() - ) - - mockStorage.write { db in - try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).insert(db) - try Identity(variant: .x25519PrivateKey, data: Data.data(fromHex: TestConstants.privateKey)!).insert(db) - try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: TestConstants.edPublicKey)!).insert(db) - try Identity(variant: .ed25519SecretKey, data: Data.data(fromHex: TestConstants.edSecretKey)!).insert(db) - - try testGroupThread.insert(db) - try testOpenGroup.insert(db) - try Capability(openGroupServer: testOpenGroup.server, variant: .sogs, isMissing: false).insert(db) - } - mockCrypto + try testGroupThread.insert(db) + try testOpenGroup.insert(db) + try Capability(openGroupServer: testOpenGroup.server, variant: .sogs, isMissing: false).insert(db) + } + ) + @TestState var mockNetwork: MockNetwork! = MockNetwork() + @TestState var mockCrypto: MockCrypto! = MockCrypto( + initialSetup: { crypto in + crypto .when { try $0.perform(.hash(message: anyArray(), outputLength: any())) } .thenReturn([]) - mockCrypto - .when { [dependencies = dependencies!] crypto in + crypto + .when { crypto in crypto.generate( .blindedKeyPair( serverPublicKey: any(), edKeyPair: any(), - using: dependencies + using: any() ) ) } @@ -174,7 +135,7 @@ class OpenGroupManagerSpec: QuickSpec { secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ) ) - mockCrypto + crypto .when { try $0.perform( .sogsSignature( @@ -186,7 +147,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } .thenReturn("TestSogsSignature".bytes) - mockCrypto + crypto .when { try $0.perform( .signature( @@ -196,59 +157,68 @@ class OpenGroupManagerSpec: QuickSpec { ) } .thenReturn("TestSignature".bytes) - mockCrypto.when { $0.size(.nonce16) }.thenReturn(16) - mockCrypto + crypto.when { $0.size(.nonce16) }.thenReturn(16) + crypto .when { try $0.perform(.generateNonce16()) } .thenReturn(Data(base64Encoded: "pK6YRtQApl4NhECGizF0Cg==")!.bytes) - mockCrypto.when { $0.size(.nonce24) }.thenReturn(24) - mockCrypto + crypto.when { $0.size(.nonce24) }.thenReturn(24) + crypto .when { try $0.perform(.generateNonce24()) } .thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes) - mockCrypto.when { $0.size(.publicKey) }.thenReturn(32) - mockOGMCache.when { $0.pendingChanges }.thenReturn([]) - mockOGMCache.when { $0.pollers = any() }.thenReturn(()) - mockOGMCache.when { $0.isPolling = any() }.thenReturn(()) - mockOGMCache + crypto.when { $0.size(.publicKey) }.thenReturn(32) + } + ) + @TestState var mockUserDefaults: MockUserDefaults! = MockUserDefaults() + @TestState var mockGeneralCache: MockGeneralCache! = MockGeneralCache( + initialSetup: { cache in + cache.when { $0.encodedPublicKey }.thenReturn("05\(TestConstants.publicKey)") + } + ) + @TestState var mockOGMCache: MockOGMCache! = MockOGMCache( + initialSetup: { cache in + cache.when { $0.pendingChanges }.thenReturn([]) + cache.when { $0.pollers = any() }.thenReturn(()) + cache.when { $0.isPolling = any() }.thenReturn(()) + cache .when { $0.defaultRoomsPublisher = any(type: [OpenGroupManager.DefaultRoomInfo].self) } .thenReturn(()) - mockOGMCache + cache .when { $0.groupImagePublishers = any(typeA: String.self, typeB: AnyPublisher.self) } .thenReturn(()) - mockOGMCache + cache .when { $0.pendingChanges = any(type: OpenGroupAPI.PendingChange.self) } .thenReturn(()) - mockGeneralCache.when { $0.encodedPublicKey }.thenReturn("05\(TestConstants.publicKey)") - - cache = OpenGroupManager.Cache() - openGroupManager = OpenGroupManager() } - + ) + @TestState var mockCaches: MockCaches! = MockCaches() + .setting(cache: .general, to: mockGeneralCache) + .setting(cache: .openGroupManager, to: mockOGMCache) + @TestState var dependencies: Dependencies! = Dependencies( + storage: mockStorage, + network: mockNetwork, + crypto: mockCrypto, + standardUserDefaults: mockUserDefaults, + caches: mockCaches, + dateNow: Date(timeIntervalSince1970: 1234567890), + forceSynchronous: true + ) + @TestState var disposables: [AnyCancellable]! = [] + + @TestState var cache: OpenGroupManager.Cache! = OpenGroupManager.Cache() + @TestState var openGroupManager: OpenGroupManager! = OpenGroupManager() + + // MARK: - an OpenGroupManager + describe("an OpenGroupManager") { + afterEach { - disposables.forEach { $0.cancel() } - - OpenGroupManager.shared.stopPolling() // Need to stop any pollers which get created during tests - openGroupManager.stopPolling() // Assuming it's different from the above - - mockStorage = nil - mockCrypto = nil - mockUserDefaults = nil - mockCaches = nil - mockGeneralCache = nil - mockOGMCache = nil - dependencies = nil - disposables = [] - - testInteraction1 = nil - testGroupThread = nil - testOpenGroup = nil - - cache = nil - openGroupManager = nil + // Just in case the shared instance had pollers created we should stop them + OpenGroupManager.shared.stopPolling() + openGroupManager.stopPolling() } - // MARK: - cache data + // MARK: -- cache data context("cache data") { - // MARK: -- defaults the time since last open to greatestFiniteMagnitude + // MARK: ---- defaults the time since last open to greatestFiniteMagnitude it("defaults the time since last open to greatestFiniteMagnitude") { mockUserDefaults .when { (defaults: inout any UserDefaultsType) -> Any? in @@ -260,7 +230,7 @@ class OpenGroupManagerSpec: QuickSpec { .to(beCloseTo(.greatestFiniteMagnitude)) } - // MARK: -- returns the time since the last open + // MARK: ---- returns the time since the last open it("returns the time since the last open") { mockUserDefaults .when { (defaults: inout any UserDefaultsType) -> Any? in @@ -273,7 +243,7 @@ class OpenGroupManagerSpec: QuickSpec { .to(beCloseTo(10)) } - // MARK: -- caches the time since the last open + // MARK: ---- caches the time since the last open it("caches the time since the last open") { mockUserDefaults .when { (defaults: inout any UserDefaultsType) -> Any? in @@ -297,7 +267,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: - when starting polling + // MARK: -- when starting polling context("when starting polling") { beforeEach { mockStorage.write { db in @@ -332,7 +302,7 @@ class OpenGroupManagerSpec: QuickSpec { .thenReturn(Date(timeIntervalSince1970: 1234567890)) } - // MARK: -- creates pollers for all of the open groups + // MARK: ---- creates pollers for all of the open groups it("creates pollers for all of the open groups") { openGroupManager.startPolling(using: dependencies) @@ -345,7 +315,7 @@ class OpenGroupManagerSpec: QuickSpec { }) } - // MARK: -- updates the isPolling flag + // MARK: ---- updates the isPolling flag it("updates the isPolling flag") { openGroupManager.startPolling(using: dependencies) @@ -353,7 +323,7 @@ class OpenGroupManagerSpec: QuickSpec { .to(call(matchingParameters: true) { $0.isPolling = true }) } - // MARK: -- does nothing if already polling + // MARK: ---- does nothing if already polling it("does nothing if already polling") { mockOGMCache.when { $0.isPolling }.thenReturn(true) @@ -363,7 +333,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: - when stopping polling + // MARK: -- when stopping polling context("when stopping polling") { beforeEach { mockStorage.write { db in @@ -385,14 +355,14 @@ class OpenGroupManagerSpec: QuickSpec { mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")]) } - // MARK: - removes all pollers + // MARK: ---- removes all pollers it("removes all pollers") { openGroupManager.stopPolling(using: dependencies) expect(mockOGMCache).to(call(matchingParameters: true) { $0.pollers = [:] }) } - // MARK: - updates the isPolling flag + // MARK: ---- updates the isPolling flag it("updates the isPolling flag") { openGroupManager.stopPolling(using: dependencies) @@ -400,87 +370,87 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: - when checking if an open group is run by session + // MARK: -- when checking if an open group is run by session context("when checking if an open group is run by session") { - // MARK: -- returns false when it does not match one of Sessions servers with no scheme + // MARK: ---- returns false when it does not match one of Sessions servers with no scheme it("returns false when it does not match one of Sessions servers with no scheme") { expect(OpenGroupManager.isSessionRunOpenGroup(server: "test.test")) .to(beFalse()) } - // MARK: -- returns false when it does not match one of Sessions servers in http + // MARK: ---- returns false when it does not match one of Sessions servers in http it("returns false when it does not match one of Sessions servers in http") { expect(OpenGroupManager.isSessionRunOpenGroup(server: "http://test.test")) .to(beFalse()) } - // MARK: -- returns false when it does not match one of Sessions servers in https + // MARK: ---- returns false when it does not match one of Sessions servers in https it("returns false when it does not match one of Sessions servers in https") { expect(OpenGroupManager.isSessionRunOpenGroup(server: "https://test.test")) .to(beFalse()) } - // MARK: -- returns true when it matches Sessions SOGS IP + // MARK: ---- returns true when it matches Sessions SOGS IP it("returns true when it matches Sessions SOGS IP") { expect(OpenGroupManager.isSessionRunOpenGroup(server: "116.203.70.33")) .to(beTrue()) } - // MARK: -- returns true when it matches Sessions SOGS IP with http + // MARK: ---- returns true when it matches Sessions SOGS IP with http it("returns true when it matches Sessions SOGS IP with http") { expect(OpenGroupManager.isSessionRunOpenGroup(server: "http://116.203.70.33")) .to(beTrue()) } - // MARK: -- returns true when it matches Sessions SOGS IP with https + // MARK: ---- returns true when it matches Sessions SOGS IP with https it("returns true when it matches Sessions SOGS IP with https") { expect(OpenGroupManager.isSessionRunOpenGroup(server: "https://116.203.70.33")) .to(beTrue()) } - // MARK: -- returns true when it matches Sessions SOGS IP with a port + // MARK: ---- returns true when it matches Sessions SOGS IP with a port it("returns true when it matches Sessions SOGS IP with a port") { expect(OpenGroupManager.isSessionRunOpenGroup(server: "116.203.70.33:80")) .to(beTrue()) } - // MARK: -- returns true when it matches Sessions SOGS domain + // MARK: ---- returns true when it matches Sessions SOGS domain it("returns true when it matches Sessions SOGS domain") { expect(OpenGroupManager.isSessionRunOpenGroup(server: "open.getsession.org")) .to(beTrue()) } - // MARK: -- returns true when it matches Sessions SOGS domain with http + // MARK: ---- returns true when it matches Sessions SOGS domain with http it("returns true when it matches Sessions SOGS domain with http") { expect(OpenGroupManager.isSessionRunOpenGroup(server: "http://open.getsession.org")) .to(beTrue()) } - // MARK: -- returns true when it matches Sessions SOGS domain with https + // MARK: ---- returns true when it matches Sessions SOGS domain with https it("returns true when it matches Sessions SOGS domain with https") { expect(OpenGroupManager.isSessionRunOpenGroup(server: "https://open.getsession.org")) .to(beTrue()) } - // MARK: -- returns true when it matches Sessions SOGS domain with a port + // MARK: ---- returns true when it matches Sessions SOGS domain with a port it("returns true when it matches Sessions SOGS domain with a port") { expect(OpenGroupManager.isSessionRunOpenGroup(server: "open.getsession.org:80")) .to(beTrue()) } } - // MARK: - when checking it has an existing open group + // MARK: -- when checking it has an existing open group context("when checking it has an existing open group") { - // MARK: -- when there is a thread for the room and the cache has a poller + // MARK: ---- when there is a thread for the room and the cache has a poller context("when there is a thread for the room and the cache has a poller") { - // MARK: ---- for the no-scheme variant + // MARK: ------ for the no-scheme variant context("for the no-scheme variant") { beforeEach { mockOGMCache.when { $0.pollers } .thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")]) } - // MARK: ------ returns true when no scheme is provided + // MARK: -------- returns true when no scheme is provided it("returns true when no scheme is provided") { expect( mockStorage.read { db -> Bool in @@ -496,7 +466,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beTrue()) } - // MARK: ------ returns true when a http scheme is provided + // MARK: -------- returns true when a http scheme is provided it("returns true when a http scheme is provided") { expect( mockStorage.read { db -> Bool in @@ -512,7 +482,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beTrue()) } - // MARK: ------ returns true when a https scheme is provided + // MARK: -------- returns true when a https scheme is provided it("returns true when a https scheme is provided") { expect( mockStorage.read { db -> Bool in @@ -529,14 +499,14 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: ---- for the http variant + // MARK: ------ for the http variant context("for the http variant") { beforeEach { mockOGMCache.when { $0.pollers } .thenReturn(["http://testserver": OpenGroupAPI.Poller(for: "http://testserver")]) } - // MARK: ------ returns true when no scheme is provided + // MARK: -------- returns true when no scheme is provided it("returns true when no scheme is provided") { expect( mockStorage.read { db -> Bool in @@ -552,7 +522,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beTrue()) } - // MARK: ------ returns true when a http scheme is provided + // MARK: -------- returns true when a http scheme is provided it("returns true when a http scheme is provided") { expect( mockStorage.read { db -> Bool in @@ -568,7 +538,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beTrue()) } - // MARK: ------ returns true when a https scheme is provided + // MARK: -------- returns true when a https scheme is provided it("returns true when a https scheme is provided") { expect( mockStorage.read { db -> Bool in @@ -585,14 +555,14 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: ---- for the https variant + // MARK: ------ for the https variant context("for the https variant") { beforeEach { mockOGMCache.when { $0.pollers } .thenReturn(["https://testserver": OpenGroupAPI.Poller(for: "https://testserver")]) } - // MARK: ------ returns true when no scheme is provided + // MARK: -------- returns true when no scheme is provided it("returns true when no scheme is provided") { expect( mockStorage.read { db -> Bool in @@ -608,7 +578,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beTrue()) } - // MARK: ------ returns true when a http scheme is provided + // MARK: -------- returns true when a http scheme is provided it("returns true when a http scheme is provided") { expect( mockStorage.read { db -> Bool in @@ -624,7 +594,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beTrue()) } - // MARK: ------ returns true when a https scheme is provided + // MARK: -------- returns true when a https scheme is provided it("returns true when a https scheme is provided") { expect( mockStorage.read { db -> Bool in @@ -642,9 +612,9 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- when given the legacy DNS host and there is a cached poller for the default server + // MARK: ---- when given the legacy DNS host and there is a cached poller for the default server context("when given the legacy DNS host and there is a cached poller for the default server") { - // MARK: ---- returns true + // MARK: ------ returns true it("returns true") { mockOGMCache.when { $0.pollers } .thenReturn(["http://116.203.70.33": OpenGroupAPI.Poller(for: "http://116.203.70.33")]) @@ -677,9 +647,9 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- when given the default server and there is a cached poller for the legacy DNS host + // MARK: ---- when given the default server and there is a cached poller for the legacy DNS host context("when given the default server and there is a cached poller for the legacy DNS host") { - // MARK: ---- returns true + // MARK: ------ returns true it("returns true") { mockOGMCache.when { $0.pollers }.thenReturn(["http://open.getsession.org": OpenGroupAPI.Poller(for: "http://open.getsession.org")]) mockStorage.write { db in @@ -711,7 +681,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- returns false when given an invalid server + // MARK: ---- returns false when given an invalid server it("returns false when given an invalid server") { mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")]) @@ -729,7 +699,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beFalse()) } - // MARK: -- returns false if there is not a poller for the server in the cache + // MARK: ---- returns false if there is not a poller for the server in the cache it("returns false if there is not a poller for the server in the cache") { mockOGMCache.when { $0.pollers }.thenReturn([:]) @@ -747,7 +717,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beFalse()) } - // MARK: -- returns false if there is a poller for the server in the cache but no thread for the room + // MARK: ---- returns false if there is a poller for the server in the cache but no thread for the room it("returns false if there is a poller for the server in the cache but no thread for the room") { mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")]) mockStorage.write { db in @@ -769,7 +739,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: - when adding + // MARK: -- when adding context("when adding") { beforeEach { mockStorage.write { db in @@ -788,7 +758,7 @@ class OpenGroupManagerSpec: QuickSpec { .thenReturn(Date(timeIntervalSince1970: 1234567890)) } - // MARK: -- stores the open group server + // MARK: ---- stores the open group server it("stores the open group server") { mockStorage .writePublisher { (db: Database) -> Bool in @@ -826,7 +796,7 @@ class OpenGroupManagerSpec: QuickSpec { .to(equal(OpenGroup.idFor(roomToken: "testRoom", server: "testServer"))) } - // MARK: -- adds a poller + // MARK: ---- adds a poller it("adds a poller") { mockStorage .writePublisher { (db: Database) -> Bool in @@ -858,7 +828,7 @@ class OpenGroupManagerSpec: QuickSpec { }) } - // MARK: -- an existing room + // MARK: ---- an existing room context("an existing room") { beforeEach { mockOGMCache.when { $0.pollers } @@ -868,7 +838,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: ---- does not reset the sequence number or update the public key + // MARK: ------ does not reset the sequence number or update the public key it("does not reset the sequence number or update the public key") { mockStorage .writePublisher { (db: Database) -> Bool in @@ -919,7 +889,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- with an invalid response + // MARK: ---- with an invalid response context("with an invalid response") { beforeEach { mockNetwork @@ -933,7 +903,7 @@ class OpenGroupManagerSpec: QuickSpec { .thenReturn(Date(timeIntervalSince1970: 1234567890)) } - // MARK: ---- fails with the error + // MARK: ------ fails with the error it("fails with the error") { var error: Error? @@ -967,7 +937,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: - when deleting + // MARK: -- when deleting context("when deleting") { beforeEach { mockStorage.write { db in @@ -988,7 +958,7 @@ class OpenGroupManagerSpec: QuickSpec { mockOGMCache.when { $0.pollers }.thenReturn([:]) } - // MARK: -- removes all interactions for the thread + // MARK: ---- removes all interactions for the thread it("removes all interactions for the thread") { mockStorage.write { db in openGroupManager @@ -1004,7 +974,7 @@ class OpenGroupManagerSpec: QuickSpec { .to(equal(0)) } - // MARK: -- removes the given thread + // MARK: ---- removes the given thread it("removes the given thread") { mockStorage.write { db in openGroupManager @@ -1020,9 +990,9 @@ class OpenGroupManagerSpec: QuickSpec { .to(equal(0)) } - // MARK: -- and there is only one open group for this server + // MARK: ---- and there is only one open group for this server context("and there is only one open group for this server") { - // MARK: ---- stops the poller + // MARK: ------ stops the poller it("stops the poller") { mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")]) @@ -1039,7 +1009,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(mockOGMCache).to(call(matchingParameters: true) { $0.pollers = [:] }) } - // MARK: ---- removes the open group + // MARK: ------ removes the open group it("removes the open group") { mockStorage.write { db in openGroupManager @@ -1056,7 +1026,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- and the are multiple open groups for this server + // MARK: ---- and the are multiple open groups for this server context("and the are multiple open groups for this server") { beforeEach { mockStorage.write { db in @@ -1080,7 +1050,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: ---- removes the open group + // MARK: ------ removes the open group it("removes the open group") { mockStorage.write { db in openGroupManager @@ -1097,7 +1067,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- and it is the default server + // MARK: ---- and it is the default server context("and it is the default server") { beforeEach { mockStorage.write { db in @@ -1135,7 +1105,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: ---- does not remove the open group + // MARK: ------ does not remove the open group it("does not remove the open group") { mockStorage.write { db in openGroupManager @@ -1151,7 +1121,7 @@ class OpenGroupManagerSpec: QuickSpec { .to(equal(2)) } - // MARK: ---- deactivates the open group + // MARK: ------ deactivates the open group it("deactivates the open group") { mockStorage.write { db in openGroupManager @@ -1176,7 +1146,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: - when handling capabilities + // MARK: -- when handling capabilities context("when handling capabilities") { beforeEach { mockStorage.write { db in @@ -1189,14 +1159,14 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- stores the capabilities + // MARK: ---- stores the capabilities it("stores the capabilities") { expect(mockStorage.read { db in try Capability.fetchCount(db) }) .to(equal(1)) } } - // MARK: - when handling room poll info + // MARK: -- when handling room poll info context("when handling room poll info") { beforeEach { mockStorage.write { db in @@ -1214,7 +1184,7 @@ class OpenGroupManagerSpec: QuickSpec { .thenReturn(nil) } - // MARK: -- saves the updated open group + // MARK: ---- saves the updated open group it("saves the updated open group") { mockStorage.write { db in try OpenGroupManager.handlePollInfo( @@ -1237,7 +1207,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(equal(10)) } - // MARK: -- calls the completion block + // MARK: ---- calls the completion block it("calls the completion block") { var didCallComplete: Bool = false @@ -1255,7 +1225,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didCallComplete).to(beTrue()) } - // MARK: -- calls the room image completion block when waiting but there is no image + // MARK: ---- calls the room image completion block when waiting but there is no image it("calls the room image completion block when waiting but there is no image") { var didCallComplete: Bool = false @@ -1274,7 +1244,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didCallComplete).to(beTrue()) } - // MARK: -- calls the room image completion block when waiting and there is an image + // MARK: ---- calls the room image completion block when waiting and there is an image it("calls the room image completion block when waiting and there is an image") { var didCallComplete: Bool = false @@ -1313,9 +1283,9 @@ class OpenGroupManagerSpec: QuickSpec { expect(didCallComplete).to(beTrue()) } - // MARK: -- and updating the moderator list + // MARK: ---- and updating the moderator list context("and updating the moderator list") { - // MARK: ---- successfully updates + // MARK: ------ successfully updates it("successfully updates") { testPollInfo = OpenGroupAPI.RoomPollInfo.mockValue.with( token: "testRoom", @@ -1361,7 +1331,7 @@ class OpenGroupManagerSpec: QuickSpec { )) } - // MARK: ---- updates for hidden moderators + // MARK: ------ updates for hidden moderators it("updates for hidden moderators") { testPollInfo = OpenGroupAPI.RoomPollInfo.mockValue.with( token: "testRoom", @@ -1407,7 +1377,7 @@ class OpenGroupManagerSpec: QuickSpec { )) } - // MARK: ---- does not insert mods if no moderators are provided + // MARK: ------ does not insert mods if no moderators are provided it("does not insert mods if no moderators are provided") { testPollInfo = OpenGroupAPI.RoomPollInfo.mockValue.with( token: "testRoom", @@ -1430,9 +1400,9 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- and updating the admin list + // MARK: ---- and updating the admin list context("and updating the admin list") { - // MARK: ---- successfully updates + // MARK: ------ successfully updates it("successfully updates") { testPollInfo = OpenGroupAPI.RoomPollInfo.mockValue.with( token: "testRoom", @@ -1478,7 +1448,7 @@ class OpenGroupManagerSpec: QuickSpec { )) } - // MARK: ---- updates for hidden admins + // MARK: ------ updates for hidden admins it("updates for hidden admins") { testPollInfo = OpenGroupAPI.RoomPollInfo.mockValue.with( token: "testRoom", @@ -1524,7 +1494,7 @@ class OpenGroupManagerSpec: QuickSpec { )) } - // MARK: ---- does not insert an admin if no admins are provided + // MARK: ------ does not insert an admin if no admins are provided it("does not insert an admin if no admins are provided") { testPollInfo = OpenGroupAPI.RoomPollInfo.mockValue.with( token: "testRoom", @@ -1548,9 +1518,9 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- when it cannot get the open group + // MARK: ---- when it cannot get the open group context("when it cannot get the open group") { - // MARK: ---- does not save the thread + // MARK: ------ does not save the thread it("does not save the thread") { mockStorage.write { db in try OpenGroup.deleteAll(db) @@ -1571,9 +1541,9 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- when not given a public key + // MARK: ---- when not given a public key context("when not given a public key") { - // MARK: ---- saves the open group with the existing public key + // MARK: ------ saves the open group with the existing public key it("saves the open group with the existing public key") { mockStorage.write { db in try OpenGroupManager.handlePollInfo( @@ -1597,9 +1567,9 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- when checking to start polling + // MARK: ---- when checking to start polling context("when checking to start polling") { - // MARK: ---- starts a new poller when not already polling + // MARK: ------ starts a new poller when not already polling it("starts a new poller when not already polling") { mockOGMCache.when { $0.pollers }.thenReturn([:]) @@ -1620,7 +1590,7 @@ class OpenGroupManagerSpec: QuickSpec { }) } - // MARK: ---- does not start a new poller when already polling + // MARK: ------ does not start a new poller when already polling it("does not start a new poller when already polling") { mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")]) @@ -1639,7 +1609,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- when trying to get the room image + // MARK: ---- when trying to get the room image context("when trying to get the room image") { beforeEach { let image: UIImage = UIImage(color: .red, size: CGSize(width: 1, height: 1)) @@ -1656,7 +1626,7 @@ class OpenGroupManagerSpec: QuickSpec { ]) } - // MARK: ---- uses the provided room image id if available + // MARK: ------ uses the provided room image id if available it("uses the provided room image id if available") { testPollInfo = OpenGroupAPI.RoomPollInfo.mockValue.with( token: "testRoom", @@ -1698,7 +1668,7 @@ class OpenGroupManagerSpec: QuickSpec { ).toNot(beNil()) } - // MARK: ---- uses the existing room image id if none is provided + // MARK: ------ uses the existing room image id if none is provided it("uses the existing room image id if none is provided") { mockStorage.write { db in try OpenGroup.deleteAll(db) @@ -1751,7 +1721,7 @@ class OpenGroupManagerSpec: QuickSpec { ).toNot(beNil()) } - // MARK: ---- uses the new room image id if there is an existing one + // MARK: ------ uses the new room image id if there is an existing one it("uses the new room image id if there is an existing one") { mockStorage.write { db in try OpenGroup.deleteAll(db) @@ -1810,7 +1780,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(mockOGMCache).to(call(.exactly(times: 1)) { $0.groupImagePublishers }) } - // MARK: ---- does nothing if there is no room image + // MARK: ------ does nothing if there is no room image it("does nothing if there is no room image") { mockStorage.write { db in try OpenGroupManager.handlePollInfo( @@ -1834,7 +1804,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beNil()) } - // MARK: ---- does nothing if it fails to retrieve the room image + // MARK: ------ does nothing if it fails to retrieve the room image it("does nothing if it fails to retrieve the room image") { mockOGMCache.when { $0.groupImagePublishers } .thenReturn([ @@ -1873,7 +1843,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beNil()) } - // MARK: ---- saves the retrieved room image + // MARK: ------ saves the retrieved room image it("saves the retrieved room image") { testPollInfo = OpenGroupAPI.RoomPollInfo.mockValue.with( token: "testRoom", @@ -1909,7 +1879,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: - when handling messages + // MARK: -- when handling messages context("when handling messages") { beforeEach { mockStorage.write { db in @@ -1919,7 +1889,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- updates the sequence number when there are messages + // MARK: ---- updates the sequence number when there are messages it("updates the sequence number when there are messages") { mockStorage.write { db in OpenGroupManager.handleMessages( @@ -1956,7 +1926,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(equal(124)) } - // MARK: -- does not update the sequence number if there are no messages + // MARK: ---- does not update the sequence number if there are no messages it("does not update the sequence number if there are no messages") { mockStorage.write { db in OpenGroupManager.handleMessages( @@ -1978,7 +1948,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(equal(5)) } - // MARK: -- ignores a message with no sender + // MARK: ---- ignores a message with no sender it("ignores a message with no sender") { mockStorage.write { db in try Interaction.deleteAll(db) @@ -2012,7 +1982,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(0)) } - // MARK: -- ignores a message with invalid data + // MARK: ---- ignores a message with invalid data it("ignores a message with invalid data") { mockStorage.write { db in try Interaction.deleteAll(db) @@ -2046,7 +2016,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(0)) } - // MARK: -- processes a message with valid data + // MARK: ---- processes a message with valid data it("processes a message with valid data") { mockStorage.write { db in OpenGroupManager.handleMessages( @@ -2061,7 +2031,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(1)) } - // MARK: -- processes valid messages when combined with invalid ones + // MARK: ---- processes valid messages when combined with invalid ones it("processes valid messages when combined with invalid ones") { mockStorage.write { db in OpenGroupManager.handleMessages( @@ -2092,9 +2062,9 @@ class OpenGroupManagerSpec: QuickSpec { expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(1)) } - // MARK: -- with no data + // MARK: ---- with no data context("with no data") { - // MARK: ---- deletes the message if we have the message + // MARK: ------ deletes the message if we have the message it("deletes the message if we have the message") { mockStorage.write { db in try Interaction @@ -2132,7 +2102,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(0)) } - // MARK: ---- does nothing if we do not have the message + // MARK: ------ does nothing if we do not have the message it("does nothing if we do not have the message") { mockStorage.write { db in OpenGroupManager.handleMessages( @@ -2164,7 +2134,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: - when handling direct messages + // MARK: -- when handling direct messages context("when handling direct messages") { beforeEach { mockCrypto @@ -2206,7 +2176,7 @@ class OpenGroupManagerSpec: QuickSpec { .thenReturn(Data(hex: TestConstants.publicKey).bytes) } - // MARK: -- does nothing if there are no messages + // MARK: ---- does nothing if there are no messages it("does nothing if there are no messages") { mockStorage.write { db in OpenGroupManager.handleDirectMessages( @@ -2236,7 +2206,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(equal(0)) } - // MARK: -- does nothing if it cannot get the open group + // MARK: ---- does nothing if it cannot get the open group it("does nothing if it cannot get the open group") { mockStorage.write { db in try OpenGroup.deleteAll(db) @@ -2270,7 +2240,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beNil()) } - // MARK: -- ignores messages with non base64 encoded data + // MARK: ---- ignores messages with non base64 encoded data it("ignores messages with non base64 encoded data") { testDirectMessage = OpenGroupAPI.DirectMessage( id: testDirectMessage.id, @@ -2294,7 +2264,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(0)) } - // MARK: -- for the inbox + // MARK: ---- for the inbox context("for the inbox") { beforeEach { mockCrypto @@ -2314,7 +2284,7 @@ class OpenGroupManagerSpec: QuickSpec { .thenReturn(false) } - // MARK: ---- updates the inbox latest message id + // MARK: ------ updates the inbox latest message id it("updates the inbox latest message id") { mockStorage.write { db in OpenGroupManager.handleDirectMessages( @@ -2336,7 +2306,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(equal(128)) } - // MARK: ---- ignores a message with invalid data + // MARK: ------ ignores a message with invalid data it("ignores a message with invalid data") { testDirectMessage = OpenGroupAPI.DirectMessage( id: testDirectMessage.id, @@ -2360,7 +2330,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(0)) } - // MARK: ---- processes a message with valid data + // MARK: ------ processes a message with valid data it("processes a message with valid data") { mockStorage.write { db in OpenGroupManager.handleDirectMessages( @@ -2375,7 +2345,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(1)) } - // MARK: ---- processes valid messages when combined with invalid ones + // MARK: ------ processes valid messages when combined with invalid ones it("processes valid messages when combined with invalid ones") { mockStorage.write { db in OpenGroupManager.handleDirectMessages( @@ -2401,7 +2371,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- for the outbox + // MARK: ---- for the outbox context("for the outbox") { beforeEach { mockCrypto @@ -2421,7 +2391,7 @@ class OpenGroupManagerSpec: QuickSpec { .thenReturn(false) } - // MARK: ---- updates the outbox latest message id + // MARK: ------ updates the outbox latest message id it("updates the outbox latest message id") { mockStorage.write { db in OpenGroupManager.handleDirectMessages( @@ -2443,7 +2413,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(equal(128)) } - // MARK: ---- retrieves an existing blinded id lookup + // MARK: ------ retrieves an existing blinded id lookup it("retrieves an existing blinded id lookup") { mockStorage.write { db in try BlindedIdLookup( @@ -2468,7 +2438,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(mockStorage.read { db -> Int in try SessionThread.fetchCount(db) }).to(equal(2)) } - // MARK: ---- falls back to using the blinded id if no lookup is found + // MARK: ------ falls back to using the blinded id if no lookup is found it("falls back to using the blinded id if no lookup is found") { mockStorage.write { db in OpenGroupManager.handleDirectMessages( @@ -2497,7 +2467,7 @@ class OpenGroupManagerSpec: QuickSpec { ).toNot(beNil()) } - // MARK: ---- ignores a message with invalid data + // MARK: ------ ignores a message with invalid data it("ignores a message with invalid data") { testDirectMessage = OpenGroupAPI.DirectMessage( id: testDirectMessage.id, @@ -2521,7 +2491,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(mockStorage.read { db -> Int in try SessionThread.fetchCount(db) }).to(equal(1)) } - // MARK: ---- processes a message with valid data + // MARK: ------ processes a message with valid data it("processes a message with valid data") { mockStorage.write { db in OpenGroupManager.handleDirectMessages( @@ -2536,7 +2506,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(mockStorage.read { db -> Int in try SessionThread.fetchCount(db) }).to(equal(2)) } - // MARK: ---- processes valid messages when combined with invalid ones + // MARK: ------ processes valid messages when combined with invalid ones it("processes valid messages when combined with invalid ones") { mockStorage.write { db in OpenGroupManager.handleDirectMessages( @@ -2563,7 +2533,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: - when determining if a user is a moderator or an admin + // MARK: -- when determining if a user is a moderator or an admin context("when determining if a user is a moderator or an admin") { beforeEach { mockStorage.write { db in @@ -2571,7 +2541,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- uses an empty set for moderators by default + // MARK: ---- uses an empty set for moderators by default it("uses an empty set for moderators by default") { expect( OpenGroupManager.isUserModeratorOrAdmin( @@ -2583,7 +2553,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beFalse()) } - // MARK: -- uses an empty set for admins by default + // MARK: ---- uses an empty set for admins by default it("uses an empty set for admins by default") { expect( OpenGroupManager.isUserModeratorOrAdmin( @@ -2595,7 +2565,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beFalse()) } - // MARK: -- returns true if the key is in the moderator set + // MARK: ---- returns true if the key is in the moderator set it("returns true if the key is in the moderator set") { mockStorage.write { db in try GroupMember( @@ -2616,7 +2586,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beTrue()) } - // MARK: -- returns true if the key is in the admin set + // MARK: ---- returns true if the key is in the admin set it("returns true if the key is in the admin set") { mockStorage.write { db in try GroupMember( @@ -2637,7 +2607,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beTrue()) } - // MARK: -- returns true if the moderator is hidden + // MARK: ---- returns true if the moderator is hidden it("returns true if the moderator is hidden") { mockStorage.write { db in try GroupMember( @@ -2658,7 +2628,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beTrue()) } - // MARK: -- returns true if the admin is hidden + // MARK: ---- returns true if the admin is hidden it("returns true if the admin is hidden") { mockStorage.write { db in try GroupMember( @@ -2679,7 +2649,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beTrue()) } - // MARK: -- returns false if the key is not a valid session id + // MARK: ---- returns false if the key is not a valid session id it("returns false if the key is not a valid session id") { expect( OpenGroupManager.isUserModeratorOrAdmin( @@ -2691,9 +2661,9 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beFalse()) } - // MARK: -- and the key is a standard session id + // MARK: ---- and the key is a standard session id context("and the key is a standard session id") { - // MARK: ---- returns false if the key is not the users session id + // MARK: ------ returns false if the key is not the users session id it("returns false if the key is not the users session id") { mockStorage.write { db in let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6") @@ -2712,7 +2682,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beFalse()) } - // MARK: ---- returns true if the key is the current users and the users unblinded id is a moderator or admin + // MARK: ------ returns true if the key is the current users and the users unblinded id is a moderator or admin it("returns true if the key is the current users and the users unblinded id is a moderator or admin") { mockStorage.write { db in let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6") @@ -2738,7 +2708,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beTrue()) } - // MARK: ---- returns true if the key is the current users and the users blinded id is a moderator or admin + // MARK: ------ returns true if the key is the current users and the users blinded id is a moderator or admin it("returns true if the key is the current users and the users blinded id is a moderator or admin") { let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6") mockCrypto @@ -2777,9 +2747,9 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- and the key is unblinded + // MARK: ---- and the key is unblinded context("and the key is unblinded") { - // MARK: ---- returns false if unable to retrieve the user ed25519 key + // MARK: ------ returns false if unable to retrieve the user ed25519 key it("returns false if unable to retrieve the user ed25519 key") { mockStorage.write { db in try Identity.filter(id: .ed25519PublicKey).deleteAll(db) @@ -2796,7 +2766,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beFalse()) } - // MARK: ---- returns false if the key is not the users unblinded id + // MARK: ------ returns false if the key is not the users unblinded id it("returns false if the key is not the users unblinded id") { mockStorage.write { db in let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6") @@ -2815,7 +2785,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beFalse()) } - // MARK: ---- returns true if the key is the current users and the users session id is a moderator or admin + // MARK: ------ returns true if the key is the current users and the users session id is a moderator or admin it("returns true if the key is the current users and the users session id is a moderator or admin") { let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6") mockGeneralCache.when { $0.encodedPublicKey }.thenReturn("05\(otherKey)") @@ -2843,7 +2813,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beTrue()) } - // MARK: ---- returns true if the key is the current users and the users blinded id is a moderator or admin + // MARK: ------ returns true if the key is the current users and the users blinded id is a moderator or admin it("returns true if the key is the current users and the users blinded id is a moderator or admin") { let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6") mockCrypto @@ -2887,9 +2857,9 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- and the key is blinded + // MARK: ---- and the key is blinded context("and the key is blinded") { - // MARK: ---- returns false if unable to retrieve the user ed25519 key + // MARK: ------ returns false if unable to retrieve the user ed25519 key it("returns false if unable to retrieve the user ed25519 key") { mockStorage.write { db in try Identity.filter(id: .ed25519PublicKey).deleteAll(db) @@ -2906,7 +2876,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beFalse()) } - // MARK: ---- returns false if unable generate a blinded key + // MARK: ------ returns false if unable generate a blinded key it("returns false if unable generate a blinded key") { mockCrypto .when { [dependencies = dependencies!] crypto in @@ -2930,7 +2900,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beFalse()) } - // MARK: ---- returns false if the key is not the users blinded id + // MARK: ------ returns false if the key is not the users blinded id it("returns false if the key is not the users blinded id") { let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6") mockCrypto @@ -2960,7 +2930,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beFalse()) } - // MARK: ---- returns true if the key is the current users and the users session id is a moderator or admin + // MARK: ------ returns true if the key is the current users and the users session id is a moderator or admin it("returns true if the key is the current users and the users session id is a moderator or admin") { let otherKey: String = TestConstants.publicKey.replacingOccurrences(of: "7", with: "6") mockGeneralCache.when { $0.encodedPublicKey }.thenReturn("05\(otherKey)") @@ -3004,7 +2974,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beTrue()) } - // MARK: ---- returns true if the key is the current users and the users unblinded id is a moderator or admin + // MARK: ------ returns true if the key is the current users and the users unblinded id is a moderator or admin it("returns true if the key is the current users and the users unblinded id is a moderator or admin") { mockCrypto .when { [dependencies = dependencies!] crypto in @@ -3050,7 +3020,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: - when getting the default rooms if needed + // MARK: -- when getting the default rooms if needed context("when getting the default rooms if needed") { beforeEach { mockNetwork @@ -3083,7 +3053,7 @@ class OpenGroupManagerSpec: QuickSpec { }.thenReturn(()) } - // MARK: -- caches the publisher if there is no cached publisher + // MARK: ---- caches the publisher if there is no cached publisher it("caches the publisher if there is no cached publisher") { let publisher = OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) @@ -3093,7 +3063,7 @@ class OpenGroupManagerSpec: QuickSpec { }) } - // MARK: -- returns the cached publisher if there is one + // MARK: ---- returns the cached publisher if there is one it("returns the cached publisher if there is one") { let uniqueRoomInstance: OpenGroupAPI.Room = OpenGroupAPI.Room.mockValue.with( token: "UniqueId", @@ -3111,7 +3081,7 @@ class OpenGroupManagerSpec: QuickSpec { .to(equal(publisher.firstValue()?.map { $0.room })) } - // MARK: -- stores the open group information + // MARK: ---- stores the open group information it("stores the open group information") { OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) @@ -3144,7 +3114,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beFalse()) } - // MARK: -- fetches rooms for the server + // MARK: ---- fetches rooms for the server it("fetches rooms for the server") { var response: [OpenGroupManager.DefaultRoomInfo]? @@ -3156,7 +3126,7 @@ class OpenGroupManagerSpec: QuickSpec { .to(equal([OpenGroupAPI.Room.mockValue])) } - // MARK: -- will retry fetching rooms 8 times before it fails + // MARK: ---- will retry fetching rooms 8 times before it fails it("will retry fetching rooms 8 times before it fails") { mockNetwork .when { $0.send(.onionRequest(any(), to: any(), with: any())) } @@ -3173,7 +3143,7 @@ class OpenGroupManagerSpec: QuickSpec { .to(call(.exactly(times: 9)) { $0.send(.onionRequest(any(), to: any(), with: any())) }) } - // MARK: -- removes the cache publisher if all retries fail + // MARK: ---- removes the cache publisher if all retries fail it("removes the cache publisher if all retries fail") { mockNetwork .when { $0.send(.onionRequest(any(), to: any(), with: any())) } @@ -3193,7 +3163,7 @@ class OpenGroupManagerSpec: QuickSpec { }) } - // MARK: -- fetches the image for any rooms with images + // MARK: ---- fetches the image for any rooms with images it("fetches the image for any rooms with images") { mockNetwork .when { @@ -3260,7 +3230,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: - when getting a room image + // MARK: -- when getting a room image context("when getting a room image") { beforeEach { mockNetwork @@ -3289,7 +3259,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - // MARK: -- retrieves the image retrieval publisher from the cache if it exists + // MARK: ---- retrieves the image retrieval publisher from the cache if it exists it("retrieves the image retrieval publisher from the cache if it exists") { let publisher = Future { resolver in resolver(Result.success(Data([5, 4, 3, 2, 1]))) @@ -3315,7 +3285,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(result).to(equal(publisher.firstValue())) } - // MARK: -- does not save the fetched image to storage + // MARK: ---- does not save the fetched image to storage it("does not save the fetched image to storage") { OpenGroupManager .roomImage( @@ -3338,7 +3308,7 @@ class OpenGroupManagerSpec: QuickSpec { ).to(beNil()) } - // MARK: -- does not update the image update timestamp + // MARK: ---- does not update the image update timestamp it("does not update the image update timestamp") { OpenGroupManager .roomImage( @@ -3359,7 +3329,7 @@ class OpenGroupManagerSpec: QuickSpec { }) } - // MARK: -- adds the image retrieval publisher to the cache + // MARK: ---- adds the image retrieval publisher to the cache it("adds the image retrieval publisher to the cache") { let publisher = OpenGroupManager .roomImage( @@ -3377,9 +3347,9 @@ class OpenGroupManagerSpec: QuickSpec { }) } - // MARK: -- for the default server + // MARK: ---- for the default server context("for the default server") { - // MARK: ---- fetches a new image if there is no cached one + // MARK: ------ fetches a new image if there is no cached one it("fetches a new image if there is no cached one") { var result: Data? @@ -3397,7 +3367,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(result).to(equal(Data([1, 2, 3]))) } - // MARK: ---- saves the fetched image to storage + // MARK: ------ saves the fetched image to storage it("saves the fetched image to storage") { OpenGroupManager .roomImage( @@ -3420,7 +3390,7 @@ class OpenGroupManagerSpec: QuickSpec { ).toNot(beNil()) } - // MARK: ---- updates the image update timestamp + // MARK: ------ updates the image update timestamp it("updates the image update timestamp") { OpenGroupManager .roomImage( @@ -3441,7 +3411,7 @@ class OpenGroupManagerSpec: QuickSpec { }) } - // MARK: ---- and there is a cached image + // MARK: ------ and there is a cached image context("and there is a cached image") { beforeEach { dependencies.dateNow = Date(timeIntervalSince1970: 1234567890) @@ -3460,7 +3430,7 @@ class OpenGroupManagerSpec: QuickSpec { }) } - // MARK: ------ retrieves the cached image + // MARK: -------- retrieves the cached image it("retrieves the cached image") { var result: Data? @@ -3478,7 +3448,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(result).to(equal(Data([2, 3, 4]))) } - // MARK: ------ fetches a new image if the cached on is older than a week + // MARK: -------- fetches a new image if the cached on is older than a week it("fetches a new image if the cached on is older than a week") { let weekInSeconds: TimeInterval = (7 * 24 * 60 * 60) let targetTimestamp: TimeInterval = ( diff --git a/SessionMessagingKitTests/Open Groups/Types/NonceGeneratorSpec.swift b/SessionMessagingKitTests/Open Groups/Types/NonceGeneratorSpec.swift index b7db2898f..ba1801ec9 100644 --- a/SessionMessagingKitTests/Open Groups/Types/NonceGeneratorSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Types/NonceGeneratorSpec.swift @@ -8,16 +8,18 @@ import Nimble @testable import SessionMessagingKit class NonceGeneratorSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - a NonceGenerator16Byte describe("a NonceGenerator16Byte") { + // MARK: -- has the correct number of bytes it("has the correct number of bytes") { expect(OpenGroupAPI.NonceGenerator16Byte().NonceBytes).to(equal(16)) } } + // MARK: - a NonceGenerator24Byte describe("a NonceGenerator24Byte") { + // MARK: -- has the correct number of bytes it("has the correct number of bytes") { expect(OpenGroupAPI.NonceGenerator24Byte().NonceBytes).to(equal(24)) } diff --git a/SessionMessagingKitTests/Open Groups/Types/PersonalizationSpec.swift b/SessionMessagingKitTests/Open Groups/Types/PersonalizationSpec.swift index f82ccaed0..ab6c201a1 100644 --- a/SessionMessagingKitTests/Open Groups/Types/PersonalizationSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Types/PersonalizationSpec.swift @@ -8,10 +8,10 @@ import Nimble @testable import SessionMessagingKit class PersonalizationSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - a Personalization describe("a Personalization") { + // MARK: -- generates bytes correctly it("generates bytes correctly") { expect(OpenGroupAPI.Personalization.sharedKeys.bytes) .to(equal([115, 111, 103, 115, 46, 115, 104, 97, 114, 101, 100, 95, 107, 101, 121, 115])) diff --git a/SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift b/SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift index 1dfb972d9..884b7473d 100644 --- a/SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift @@ -8,10 +8,10 @@ import Nimble @testable import SessionMessagingKit class SOGSEndpointSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - a SOGSEndpoint describe("a SOGSEndpoint") { + // MARK: -- generates the path value correctly it("generates the path value correctly") { // Utility diff --git a/SessionMessagingKitTests/Open Groups/Types/SOGSErrorSpec.swift b/SessionMessagingKitTests/Open Groups/Types/SOGSErrorSpec.swift index cba429cee..c2bc0307c 100644 --- a/SessionMessagingKitTests/Open Groups/Types/SOGSErrorSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Types/SOGSErrorSpec.swift @@ -8,10 +8,10 @@ import Nimble @testable import SessionMessagingKit class SOGSErrorSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - a SOGSError describe("a SOGSError") { + // MARK: -- generates the error description correctly it("generates the error description correctly") { expect(OpenGroupAPIError.decryptionFailed.errorDescription) .to(equal("Couldn't decrypt response.")) diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift index 4429b0ecd..da3e5c94b 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift @@ -11,45 +11,35 @@ import Nimble @testable import SessionMessagingKit class MessageReceiverDecryptionSpec: QuickSpec { - // MARK: - Spec - - override func spec() { - var mockStorage: Storage! - var mockCrypto: MockCrypto! - var dependencies: Dependencies! + override class func spec() { + // MARK: Configuration - describe("a MessageReceiver") { - beforeEach { - mockStorage = SynchronousStorage( - customWriter: try! DatabaseQueue(), - customMigrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ] - ) - mockCrypto = MockCrypto() - dependencies = Dependencies( - storage: mockStorage, - crypto: mockCrypto - ) - - mockStorage.write { db in - try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db) - try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db) - } - mockCrypto - .when { [dependencies = dependencies!] crypto in + @TestState var mockStorage: Storage! = SynchronousStorage( + customWriter: try! DatabaseQueue(), + customMigrationTargets: [ + SNUtilitiesKit.self, + SNMessagingKit.self + ], + initialData: { db in + try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db) + try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db) + } + ) + @TestState var mockCrypto: MockCrypto! = MockCrypto( + initialSetup: { crypto in + crypto + .when { crypto in try crypto.perform( .encryptAeadXChaCha20( message: anyArray(), secretKey: anyArray(), nonce: anyArray(), - using: dependencies + using: any() ) ) } .thenReturn(nil) - mockCrypto + crypto .when { try $0.perform( .open( @@ -60,13 +50,13 @@ class MessageReceiverDecryptionSpec: QuickSpec { ) } .thenReturn([UInt8](repeating: 0, count: 100)) - mockCrypto - .when { [dependencies = dependencies!] crypto in + crypto + .when { crypto in crypto.generate( .blindedKeyPair( serverPublicKey: any(), edKeyPair: any(), - using: dependencies + using: any() ) ) } @@ -76,34 +66,36 @@ class MessageReceiverDecryptionSpec: QuickSpec { secretKey: Data(hex: TestConstants.edSecretKey).bytes ) ) - mockCrypto - .when { [dependencies = dependencies!] crypto in + crypto + .when { crypto in try crypto.perform( .sharedBlindedEncryptionKey( secretKey: anyArray(), otherBlindedPublicKey: anyArray(), fromBlindedPublicKey: anyArray(), toBlindedPublicKey: anyArray(), - using: dependencies + using: any() ) ) } .thenReturn([]) - mockCrypto - .when { [dependencies = dependencies!] crypto in - try crypto.perform(.generateBlindingFactor(serverPublicKey: any(), using: dependencies)) + crypto + .when { crypto in + try crypto.perform( + .generateBlindingFactor(serverPublicKey: any(), using: any()) + ) } .thenReturn([]) - mockCrypto + crypto .when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) } .thenReturn(Data(hex: TestConstants.blindedPublicKey).bytes) - mockCrypto + crypto .when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) } .thenReturn(Data(hex: TestConstants.publicKey).bytes) - mockCrypto + crypto .when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) } .thenReturn(true) - mockCrypto + crypto .when { try $0.perform( .decryptAeadXChaCha20( @@ -114,15 +106,24 @@ class MessageReceiverDecryptionSpec: QuickSpec { ) } .thenReturn("TestMessage".data(using: .utf8)!.bytes + [UInt8](repeating: 0, count: 32)) - mockCrypto.when { $0.size(.nonce24) }.thenReturn(24) - mockCrypto.when { $0.size(.publicKey) }.thenReturn(32) - mockCrypto.when { $0.size(.signature) }.thenReturn(64) - mockCrypto + crypto.when { $0.size(.nonce24) }.thenReturn(24) + crypto.when { $0.size(.publicKey) }.thenReturn(32) + crypto.when { $0.size(.signature) }.thenReturn(64) + crypto .when { try $0.perform(.generateNonce24()) } .thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes) } - + ) + @TestState var dependencies: Dependencies! = Dependencies( + storage: mockStorage, + crypto: mockCrypto + ) + + // MARK: - a MessageReceiver + describe("a MessageReceiver") { + // MARK: -- when decrypting with the session protocol context("when decrypting with the session protocol") { + // MARK: ---- successfully decrypts a message it("successfully decrypts a message") { let result = try? MessageReceiver.decryptWithSessionProtocol( ciphertext: Data( @@ -142,6 +143,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) } + // MARK: ---- throws an error if it cannot open the message it("throws an error if it cannot open the message") { mockCrypto .when { @@ -168,6 +170,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(throwError(MessageReceiverError.decryptionFailed)) } + // MARK: ---- throws an error if the open message is too short it("throws an error if the open message is too short") { mockCrypto .when { @@ -194,6 +197,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(throwError(MessageReceiverError.decryptionFailed)) } + // MARK: ---- throws an error if it cannot verify the message it("throws an error if it cannot verify the message") { mockCrypto .when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) } @@ -212,6 +216,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(throwError(MessageReceiverError.invalidSignature)) } + // MARK: ---- throws an error if it cannot get the senders x25519 public key it("throws an error if it cannot get the senders x25519 public key") { mockCrypto.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }.thenReturn(nil) @@ -229,7 +234,9 @@ class MessageReceiverDecryptionSpec: QuickSpec { } } + // MARK: -- when decrypting with the blinded session protocol context("when decrypting with the blinded session protocol") { + // MARK: ---- successfully decrypts a message it("successfully decrypts a message") { let result = try? MessageReceiver.decryptWithSessionBlindingProtocol( data: Data( @@ -252,6 +259,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) } + // MARK: ---- successfully decrypts a mocked incoming message it("successfully decrypts a mocked incoming message") { let result = try? MessageReceiver.decryptWithSessionBlindingProtocol( data: ( @@ -274,6 +282,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) } + // MARK: ---- throws an error if the data is too short it("throws an error if the data is too short") { expect { try MessageReceiver.decryptWithSessionBlindingProtocol( @@ -291,6 +300,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(throwError(MessageReceiverError.decryptionFailed)) } + // MARK: ---- throws an error if it cannot get the blinded keyPair it("throws an error if it cannot get the blinded keyPair") { mockCrypto .when { [dependencies = dependencies!] crypto in @@ -324,6 +334,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(throwError(MessageReceiverError.decryptionFailed)) } + // MARK: ---- throws an error if it cannot get the decryption key it("throws an error if it cannot get the decryption key") { mockCrypto .when { [dependencies = dependencies!] crypto in @@ -359,6 +370,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(throwError(MessageReceiverError.decryptionFailed)) } + // MARK: ---- throws an error if the data version is not 0 it("throws an error if the data version is not 0") { expect { try MessageReceiver.decryptWithSessionBlindingProtocol( @@ -380,6 +392,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(throwError(MessageReceiverError.decryptionFailed)) } + // MARK: ---- throws an error if it cannot decrypt the data it("throws an error if it cannot decrypt the data") { mockCrypto .when { @@ -413,6 +426,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(throwError(MessageReceiverError.decryptionFailed)) } + // MARK: ---- throws an error if the inner bytes are too short it("throws an error if the inner bytes are too short") { mockCrypto .when { @@ -446,6 +460,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(throwError(MessageReceiverError.decryptionFailed)) } + // MARK: ---- throws an error if it cannot generate the blinding factor it("throws an error if it cannot generate the blinding factor") { mockCrypto .when { [dependencies = dependencies!] crypto in @@ -473,6 +488,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(throwError(MessageReceiverError.invalidSignature)) } + // MARK: ---- throws an error if it cannot generate the combined key it("throws an error if it cannot generate the combined key") { mockCrypto .when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) } @@ -498,6 +514,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(throwError(MessageReceiverError.invalidSignature)) } + // MARK: ---- throws an error if the combined key does not match kA it("throws an error if the combined key does not match kA") { mockCrypto .when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) } @@ -523,6 +540,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { .to(throwError(MessageReceiverError.invalidSignature)) } + // MARK: ---- throws an error if it cannot get the senders x25519 public key it("throws an error if it cannot get the senders x25519 public key") { mockCrypto .when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) } diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift index 27510a4f5..2f1b56c49 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift @@ -11,41 +11,35 @@ import Nimble @testable import SessionMessagingKit class MessageSenderEncryptionSpec: QuickSpec { - // MARK: - Spec - - override func spec() { - var mockStorage: Storage! - var mockCrypto: MockCrypto! - var dependencies: Dependencies! + override class func spec() { + // MARK: Configuration - describe("a MessageSender") { - // MARK: - Configuration - - beforeEach { - mockStorage = SynchronousStorage( - customWriter: try! DatabaseQueue(), - customMigrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ] - ) - mockCrypto = MockCrypto() - - dependencies = Dependencies( - storage: mockStorage, - crypto: mockCrypto - ) - - mockStorage.write { db in - try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db) - try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db) - } - mockCrypto + @TestState var mockStorage: Storage! = SynchronousStorage( + customWriter: try! DatabaseQueue(), + customMigrationTargets: [ + SNUtilitiesKit.self, + SNMessagingKit.self + ], + initialData: { db in + try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db) + try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db) + } + ) + @TestState var mockCrypto: MockCrypto! = MockCrypto( + initialSetup: { crypto in + crypto .when { try $0.perform(.generateNonce24()) } .thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes) } - - // MARK: - when encrypting with the session protocol + ) + @TestState var dependencies: Dependencies! = Dependencies( + storage: mockStorage, + crypto: mockCrypto + ) + + // MARK: - a MessageSender + describe("a MessageSender") { + // MARK: -- when encrypting with the session protocol context("when encrypting with the session protocol") { beforeEach { mockCrypto @@ -56,7 +50,7 @@ class MessageSenderEncryptionSpec: QuickSpec { .thenReturn([]) } - // MARK: -- can encrypt correctly + // MARK: ---- can encrypt correctly it("can encrypt correctly") { let result: Data? = mockStorage.read { db in try? MessageSender.encryptWithSessionProtocol( @@ -72,7 +66,7 @@ class MessageSenderEncryptionSpec: QuickSpec { expect(result?.count).to(equal(155)) } - // MARK: -- returns the correct value when mocked + // MARK: ---- returns the correct value when mocked it("returns the correct value when mocked") { let result: Data? = mockStorage.read { db in try? MessageSender.encryptWithSessionProtocol( @@ -86,7 +80,7 @@ class MessageSenderEncryptionSpec: QuickSpec { expect(result?.bytes).to(equal([1, 2, 3])) } - // MARK: -- throws an error if there is no ed25519 keyPair + // MARK: ---- throws an error if there is no ed25519 keyPair it("throws an error if there is no ed25519 keyPair") { mockStorage.write { db in _ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db) @@ -106,7 +100,7 @@ class MessageSenderEncryptionSpec: QuickSpec { } } - // MARK: -- throws an error if the signature generation fails + // MARK: ---- throws an error if the signature generation fails it("throws an error if the signature generation fails") { mockCrypto .when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) } @@ -125,7 +119,7 @@ class MessageSenderEncryptionSpec: QuickSpec { } } - // MARK: -- throws an error if the encryption fails + // MARK: ---- throws an error if the encryption fails it("throws an error if the encryption fails") { mockCrypto .when { try $0.perform(.seal(message: anyArray(), recipientPublicKey: anyArray())) } @@ -145,7 +139,7 @@ class MessageSenderEncryptionSpec: QuickSpec { } } - // MARK: - when encrypting with the blinded session protocol + // MARK: -- when encrypting with the blinded session protocol context("when encrypting with the blinded session protocol") { beforeEach { mockCrypto @@ -186,7 +180,7 @@ class MessageSenderEncryptionSpec: QuickSpec { .thenReturn([2, 3, 4]) } - // MARK: -- can encrypt correctly + // MARK: ---- can encrypt correctly it("can encrypt correctly") { let result: Data? = mockStorage.read { db in try? MessageSender.encryptWithSessionBlindingProtocol( @@ -203,7 +197,7 @@ class MessageSenderEncryptionSpec: QuickSpec { expect(result?.count).to(equal(84)) } - // MARK: -- returns the correct value when mocked + // MARK: ---- returns the correct value when mocked it("returns the correct value when mocked") { let result: Data? = mockStorage.read { db in try? MessageSender.encryptWithSessionBlindingProtocol( @@ -219,7 +213,7 @@ class MessageSenderEncryptionSpec: QuickSpec { .to(equal("00020304a5b4d48b3ade4f4b2a2764762e5a2c7900f254bd91633b43")) } - // MARK: -- includes a version at the start of the encrypted value + // MARK: ---- includes a version at the start of the encrypted value it("includes a version at the start of the encrypted value") { let result: Data? = mockStorage.read { db in try? MessageSender.encryptWithSessionBlindingProtocol( @@ -234,7 +228,7 @@ class MessageSenderEncryptionSpec: QuickSpec { expect(result?.toHexString().prefix(2)).to(equal("00")) } - // MARK: -- includes the nonce at the end of the encrypted value + // MARK: ---- includes the nonce at the end of the encrypted value it("includes the nonce at the end of the encrypted value") { let maybeResult: Data? = mockStorage.read { db in try? MessageSender.encryptWithSessionBlindingProtocol( @@ -252,7 +246,7 @@ class MessageSenderEncryptionSpec: QuickSpec { .to(equal("pbTUizreT0sqJ2R2LloseQDyVL2RYztD")) } - // MARK: -- throws an error if the recipient isn't a blinded id + // MARK: ---- throws an error if the recipient isn't a blinded id it("throws an error if the recipient isn't a blinded id") { mockStorage.read { db in expect { @@ -268,7 +262,7 @@ class MessageSenderEncryptionSpec: QuickSpec { } } - // MARK: -- throws an error if there is no ed25519 keyPair + // MARK: ---- throws an error if there is no ed25519 keyPair it("throws an error if there is no ed25519 keyPair") { mockStorage.write { db in _ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db) @@ -289,7 +283,7 @@ class MessageSenderEncryptionSpec: QuickSpec { } } - // MARK: -- throws an error if it fails to generate a blinded keyPair + // MARK: ---- throws an error if it fails to generate a blinded keyPair it("throws an error if it fails to generate a blinded keyPair") { mockCrypto .when { [dependencies = dependencies!] crypto in @@ -317,7 +311,7 @@ class MessageSenderEncryptionSpec: QuickSpec { } } - // MARK: -- throws an error if it fails to generate an encryption key + // MARK: ---- throws an error if it fails to generate an encryption key it("throws an error if it fails to generate an encryption key") { mockCrypto .when { [dependencies = dependencies!] crypto in @@ -347,7 +341,7 @@ class MessageSenderEncryptionSpec: QuickSpec { } } - // MARK: -- throws an error if it fails to encrypt + // MARK: ---- throws an error if it fails to encrypt it("throws an error if it fails to encrypt") { mockCrypto .when { diff --git a/SessionMessagingKitTests/Shared Models/SessionThreadViewModelSpec.swift b/SessionMessagingKitTests/Shared Models/SessionThreadViewModelSpec.swift index df58e26c3..4178346b0 100644 --- a/SessionMessagingKitTests/Shared Models/SessionThreadViewModelSpec.swift +++ b/SessionMessagingKitTests/Shared Models/SessionThreadViewModelSpec.swift @@ -9,57 +9,42 @@ import SessionUtilitiesKit @testable import SessionMessagingKit class SessionThreadViewModelSpec: QuickSpec { - public struct TestMessage: Codable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { - public static var databaseTableName: String { "testMessage" } + override class func spec() { + // MARK: Configuration - public typealias Columns = CodingKeys - public enum CodingKeys: String, CodingKey, ColumnExpression { - case body - } - - public let body: String - } - - // MARK: - Spec - - override func spec() { - describe("a SessionThreadViewModel") { - var mockStorage: Storage! - - beforeEach { - mockStorage = SynchronousStorage( - customWriter: try! DatabaseQueue() - ) + @TestState var mockStorage: Storage! = SynchronousStorage( + customWriter: try! DatabaseQueue(), + initialData: { db in + try db.create(table: TestMessage.self) { t in + t.column(.body, .text).notNull() + } - mockStorage.write { db in - try db.create(table: TestMessage.self) { t in - t.column(.body, .text).notNull() - } + try db.create(virtualTable: TestMessage.fullTextSearchTableName, using: FTS5()) { t in + t.synchronize(withTable: TestMessage.databaseTableName) + t.tokenizer = .porter(wrapping: .unicode61()) - try db.create(virtualTable: TestMessage.fullTextSearchTableName, using: FTS5()) { t in - t.synchronize(withTable: TestMessage.databaseTableName) - t.tokenizer = .porter(wrapping: .unicode61()) - - t.column(TestMessage.Columns.body.name) - } + t.column(TestMessage.Columns.body.name) } } - - // MARK: - when processing a search term + ) + + // MARK: - a SessionThreadViewModel + describe("a SessionThreadViewModel") { + // MARK: -- when processing a search term context("when processing a search term") { - // MARK: -- correctly generates a safe search term + // MARK: ---- correctly generates a safe search term it("correctly generates a safe search term") { expect(SessionThreadViewModel.searchSafeTerm("Test")).to(equal("\"Test\"")) } - // MARK: -- standardises odd quote characters + // MARK: ---- standardises odd quote characters it("standardises odd quote characters") { expect(SessionThreadViewModel.standardQuotes("\"")).to(equal("\"")) expect(SessionThreadViewModel.standardQuotes("”")).to(equal("\"")) expect(SessionThreadViewModel.standardQuotes("“")).to(equal("\"")) } - // MARK: -- splits on the space character + // MARK: ---- splits on the space character it("splits on the space character") { expect(SessionThreadViewModel.searchTermParts("Test Message")) .to(equal([ @@ -68,7 +53,7 @@ class SessionThreadViewModelSpec: QuickSpec { ])) } - // MARK: -- surrounds each split term with quotes + // MARK: ---- surrounds each split term with quotes it("surrounds each split term with quotes") { expect(SessionThreadViewModel.searchTermParts("Test Message")) .to(equal([ @@ -77,7 +62,7 @@ class SessionThreadViewModelSpec: QuickSpec { ])) } - // MARK: -- keeps words within quotes together + // MARK: ---- keeps words within quotes together it("keeps words within quotes together") { expect(SessionThreadViewModel.searchTermParts("This \"is a Test\" Message")) .to(equal([ @@ -113,7 +98,7 @@ class SessionThreadViewModelSpec: QuickSpec { ])) } - // MARK: -- keeps words within weird quotes together + // MARK: ---- keeps words within weird quotes together it("keeps words within weird quotes together") { expect(SessionThreadViewModel.searchTermParts("This ”is a Test“ Message")) .to(equal([ @@ -123,7 +108,7 @@ class SessionThreadViewModelSpec: QuickSpec { ])) } - // MARK: -- removes extra whitespace + // MARK: ---- removes extra whitespace it("removes extra whitespace") { expect(SessionThreadViewModel.searchTermParts(" Test Message ")) .to(equal([ @@ -133,7 +118,7 @@ class SessionThreadViewModelSpec: QuickSpec { } } - // MARK: - when searching + // MARK: -- when searching context("when searching") { beforeEach { mockStorage.write { db in @@ -156,7 +141,7 @@ class SessionThreadViewModelSpec: QuickSpec { } } - // MARK: -- returns results + // MARK: ---- returns results it("returns results") { let results = mockStorage.read { db in let pattern: FTS5Pattern = try SessionThreadViewModel.pattern( @@ -186,7 +171,7 @@ class SessionThreadViewModelSpec: QuickSpec { ])) } - // MARK: -- adds a wildcard to the final part + // MARK: ---- adds a wildcard to the final part it("adds a wildcard to the final part") { let results = mockStorage.read { db in let pattern: FTS5Pattern = try SessionThreadViewModel.pattern( @@ -216,7 +201,7 @@ class SessionThreadViewModelSpec: QuickSpec { ])) } - // MARK: -- does not add a wildcard to other parts + // MARK: ---- does not add a wildcard to other parts it("does not add a wildcard to other parts") { let results = mockStorage.read { db in let pattern: FTS5Pattern = try SessionThreadViewModel.pattern( @@ -239,7 +224,7 @@ class SessionThreadViewModelSpec: QuickSpec { .to(beEmpty()) } - // MARK: -- finds similar words without the wildcard due to the porter tokenizer + // MARK: ---- finds similar words without the wildcard due to the porter tokenizer it("finds similar words without the wildcard due to the porter tokenizer") { let results = mockStorage.read { db in let pattern: FTS5Pattern = try SessionThreadViewModel.pattern( @@ -271,7 +256,7 @@ class SessionThreadViewModelSpec: QuickSpec { ])) } - // MARK: -- finds results containing the words regardless of the order + // MARK: ---- finds results containing the words regardless of the order it("finds results containing the words regardless of the order") { let results = mockStorage.read { db in let pattern: FTS5Pattern = try SessionThreadViewModel.pattern( @@ -303,7 +288,7 @@ class SessionThreadViewModelSpec: QuickSpec { ])) } - // MARK: -- does not find quoted parts out of order + // MARK: ---- does not find quoted parts out of order it("does not find quoted parts out of order") { let results = mockStorage.read { db in let pattern: FTS5Pattern = try SessionThreadViewModel.pattern( @@ -332,3 +317,16 @@ class SessionThreadViewModelSpec: QuickSpec { } } } + +// MARK: - Test Types + +fileprivate struct TestMessage: Codable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { + public static var databaseTableName: String { "testMessage" } + + public typealias Columns = CodingKeys + public enum CodingKeys: String, CodingKey, ColumnExpression { + case body + } + + public let body: String +} diff --git a/SessionMessagingKitTests/Utilities/CryptoSMKSpec.swift b/SessionMessagingKitTests/Utilities/CryptoSMKSpec.swift index 5db9f95b9..b05eb335b 100644 --- a/SessionMessagingKitTests/Utilities/CryptoSMKSpec.swift +++ b/SessionMessagingKitTests/Utilities/CryptoSMKSpec.swift @@ -10,24 +10,18 @@ import Nimble @testable import SessionMessagingKit class CryptoSMKSpec: QuickSpec { - // MARK: - Spec - - override func spec() { - var crypto: Crypto! - var mockCrypto: MockCrypto! - var dependencies: Dependencies! + override class func spec() { + // MARK: Configuration - beforeEach { - crypto = Crypto() - mockCrypto = MockCrypto() - dependencies = Dependencies(crypto: crypto) - } + @TestState var crypto: Crypto! = Crypto() + @TestState var mockCrypto: MockCrypto! = MockCrypto() + @TestState var dependencies: Dependencies! = Dependencies(crypto: crypto) + // MARK: - Crypto for SessionMessagingKit describe("Crypto for SessionMessagingKit") { - - // MARK: - when extending Sign + // MARK: -- when extending Sign context("when extending Sign") { - // MARK: -- can convert an ed25519 public key into an x25519 public key + // MARK: ---- can convert an ed25519 public key into an x25519 public key it("can convert an ed25519 public key into an x25519 public key") { let result = try? crypto.perform(.toX25519(ed25519PublicKey: TestConstants.edPublicKey.bytes)) @@ -35,7 +29,7 @@ class CryptoSMKSpec: QuickSpec { .to(equal("95ffb559d4e804e9b414a5178454c426f616b4a61089b217b41165dbb7c9fe2d")) } - // MARK: -- can convert an ed25519 private key into an x25519 private key + // MARK: ---- can convert an ed25519 private key into an x25519 private key it("can convert an ed25519 private key into an x25519 private key") { let result = try? crypto.perform(.toX25519(ed25519SecretKey: TestConstants.edSecretKey.bytes)) @@ -44,11 +38,11 @@ class CryptoSMKSpec: QuickSpec { } } - // MARK: - when extending Sodium + // MARK: -- when extending Sodium context("when extending Sodium") { - // MARK: -- and generating a blinding factor + // MARK: ---- and generating a blinding factor context("and generating a blinding factor") { - // MARK: --- successfully generates a blinding factor + // MARK: ------ successfully generates a blinding factor it("successfully generates a blinding factor") { let result = try? crypto.perform( .generateBlindingFactor( @@ -61,7 +55,7 @@ class CryptoSMKSpec: QuickSpec { .to(equal("84e3eb75028a9b73fec031b7448e322a68ca6485fad81ab1bead56f759ebeb0f")) } - // MARK: --- fails if the serverPublicKey is not a hex string + // MARK: ------ fails if the serverPublicKey is not a hex string it("fails if the serverPublicKey is not a hex string") { let result = try? crypto.perform( .generateBlindingFactor( @@ -73,7 +67,7 @@ class CryptoSMKSpec: QuickSpec { expect(result).to(beNil()) } - // MARK: --- fails if it cannot hash the serverPublicKey bytes + // MARK: ------ fails if it cannot hash the serverPublicKey bytes it("fails if it cannot hash the serverPublicKey bytes") { dependencies = Dependencies(crypto: mockCrypto) mockCrypto @@ -91,9 +85,9 @@ class CryptoSMKSpec: QuickSpec { } } - // MARK: -- and generating a blinded key pair + // MARK: ---- and generating a blinded key pair context("and generating a blinded key pair") { - // MARK: --- successfully generates a blinded key pair + // MARK: ------ successfully generates a blinded key pair it("successfully generates a blinded key pair") { let result = crypto.generate( .blindedKeyPair( @@ -112,7 +106,7 @@ class CryptoSMKSpec: QuickSpec { .to(equal("16663322d6b684e1c9dcc02b9e8642c3affd3bc431a9ea9e63dbbac88ce7a305")) } - // MARK: --- fails if the edKeyPair public key length wrong + // MARK: ------ fails if the edKeyPair public key length wrong it("fails if the edKeyPair public key length wrong") { let result = crypto.generate( .blindedKeyPair( @@ -128,7 +122,7 @@ class CryptoSMKSpec: QuickSpec { expect(result).to(beNil()) } - // MARK: --- fails if the edKeyPair secret key length wrong + // MARK: ------ fails if the edKeyPair secret key length wrong it("fails if the edKeyPair secret key length wrong") { let result = crypto.generate( .blindedKeyPair( @@ -144,7 +138,7 @@ class CryptoSMKSpec: QuickSpec { expect(result).to(beNil()) } - // MARK: --- fails if it cannot generate a blinding factor + // MARK: ------ fails if it cannot generate a blinding factor it("fails if it cannot generate a blinding factor") { let result = crypto.generate( .blindedKeyPair( @@ -161,9 +155,9 @@ class CryptoSMKSpec: QuickSpec { } } - // MARK: -- and generating a sogsSignature + // MARK: ---- and generating a sogsSignature context("and generating a sogsSignature") { - // MARK: --- generates a correct signature + // MARK: ------ generates a correct signature it("generates a correct signature") { let result = try? crypto.perform( .sogsSignature( @@ -182,9 +176,9 @@ class CryptoSMKSpec: QuickSpec { } } - // MARK: -- and combining keys + // MARK: ---- and combining keys context("and combining keys") { - // MARK: --- generates a correct combined key + // MARK: ------ generates a correct combined key it("generates a correct combined key") { let result = try? crypto.perform( .combineKeys( @@ -198,9 +192,9 @@ class CryptoSMKSpec: QuickSpec { } } - // MARK: -- and creating a shared blinded encryption key + // MARK: ---- and creating a shared blinded encryption key context("and creating a shared blinded encryption key") { - // MARK: --- generates a correct combined key + // MARK: ------ generates a correct combined key it("generates a correct combined key") { let result = try? crypto.perform( .sharedBlindedEncryptionKey( @@ -216,7 +210,7 @@ class CryptoSMKSpec: QuickSpec { .to(equal("388ee09e4c356b91f1cce5cc0aa0cf59e8e8cade69af61685d09c2d2731bc99e")) } - // MARK: --- fails if the scalar multiplication fails + // MARK: ------ fails if the scalar multiplication fails it("fails if the scalar multiplication fails") { let result = try? crypto.perform( .sharedBlindedEncryptionKey( @@ -232,9 +226,9 @@ class CryptoSMKSpec: QuickSpec { } } - // MARK: -- and checking if a session id matches a blinded id + // MARK: ---- and checking if a session id matches a blinded id context("and checking if a session id matches a blinded id") { - // MARK: --- returns true when they match + // MARK: ------ returns true when they match it("returns true when they match") { let result = crypto.verify( .sessionId( @@ -248,7 +242,7 @@ class CryptoSMKSpec: QuickSpec { expect(result).to(beTrue()) } - // MARK: --- returns false if given an invalid session id + // MARK: ------ returns false if given an invalid session id it("returns false if given an invalid session id") { let result = crypto.verify( .sessionId( @@ -262,7 +256,7 @@ class CryptoSMKSpec: QuickSpec { expect(result).to(beFalse()) } - // MARK: --- returns false if given an invalid blinded id + // MARK: ------ returns false if given an invalid blinded id it("returns false if given an invalid blinded id") { let result = crypto.verify( .sessionId( @@ -276,7 +270,7 @@ class CryptoSMKSpec: QuickSpec { expect(result).to(beFalse()) } - // MARK: --- returns false if it fails to generate the blinding factor + // MARK: ------ returns false if it fails to generate the blinding factor it("returns false if it fails to generate the blinding factor") { let result = crypto.verify( .sessionId( @@ -292,11 +286,11 @@ class CryptoSMKSpec: QuickSpec { } } - // MARK: - when extending GenericHash + // MARK: -- when extending GenericHash describe("when extending GenericHash") { - // MARK: -- and generating a hash with salt and personal values + // MARK: ---- and generating a hash with salt and personal values context("and generating a hash with salt and personal values") { - // MARK: --- generates a hash correctly + // MARK: ------ generates a hash correctly it("generates a hash correctly") { let result = try? crypto.perform( .hashSaltPersonal( @@ -312,7 +306,7 @@ class CryptoSMKSpec: QuickSpec { expect(result?.count).to(equal(32)) } - // MARK: --- generates a hash correctly with no key + // MARK: ------ generates a hash correctly with no key it("generates a hash correctly with no key") { let result = try? crypto.perform( .hashSaltPersonal( @@ -328,7 +322,7 @@ class CryptoSMKSpec: QuickSpec { expect(result?.count).to(equal(32)) } - // MARK: --- fails if given invalid options + // MARK: ------ fails if given invalid options it("fails if given invalid options") { let result = try? crypto.perform( .hashSaltPersonal( @@ -345,11 +339,11 @@ class CryptoSMKSpec: QuickSpec { } } - // MARK: - when extending AeadXChaCha20Poly1305Ietf + // MARK: -- when extending AeadXChaCha20Poly1305Ietf context("when extending AeadXChaCha20Poly1305Ietf") { - // MARK: -- when encrypting + // MARK: ---- when encrypting context("when encrypting") { - // MARK: --- encrypts correctly + // MARK: ------ encrypts correctly it("encrypts correctly") { let result = try? crypto.perform( .encryptAeadXChaCha20( @@ -365,7 +359,7 @@ class CryptoSMKSpec: QuickSpec { expect(result?.count).to(equal(27)) } - // MARK: --- encrypts correctly with additional data + // MARK: ------ encrypts correctly with additional data it("encrypts correctly with additional data") { let result = try? crypto.perform( .encryptAeadXChaCha20( @@ -381,7 +375,7 @@ class CryptoSMKSpec: QuickSpec { expect(result?.count).to(equal(27)) } - // MARK: --- fails if given an invalid key + // MARK: ------ fails if given an invalid key it("fails if given an invalid key") { let result = try? crypto.perform( .encryptAeadXChaCha20( diff --git a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift index 6f2e5fbc3..60d648fdb 100644 --- a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift @@ -11,77 +11,58 @@ import SessionUtilitiesKit @testable import Session class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { - typealias ParentType = SessionTableViewModel - - // MARK: - Spec - - override func spec() { - var mockStorage: Storage! - var cancellables: [AnyCancellable] = [] - var dependencies: Dependencies! - var viewModel: ThreadDisappearingMessagesSettingsViewModel! + override class func spec() { + // MARK: Configuration + @TestState var mockStorage: Storage! = SynchronousStorage( + customWriter: try! DatabaseQueue(), + customMigrationTargets: [ + SNUtilitiesKit.self, + SNSnodeKit.self, + SNMessagingKit.self, + SNUIKit.self + ], + initialData: { db in + try SessionThread( + id: "TestId", + variant: .contact + ).insert(db) + } + ) + @TestState var dependencies: Dependencies! = Dependencies( + storage: mockStorage, + scheduler: .immediate + ) + @TestState var viewModel: ThreadDisappearingMessagesSettingsViewModel! = ThreadDisappearingMessagesSettingsViewModel( + threadId: "TestId", + threadVariant: .contact, + config: DisappearingMessagesConfiguration.defaultWith("TestId"), + using: dependencies + ) + + @TestState var cancellables: [AnyCancellable]! = [ + viewModel.observableTableData + .receive(on: ImmediateScheduler.shared) + .sink( + receiveCompletion: { _ in }, + receiveValue: { viewModel.updateTableData($0.0) } + ) + ] + + // MARK: - a ThreadDisappearingMessagesSettingsViewModel describe("a ThreadDisappearingMessagesSettingsViewModel") { - // MARK: - Configuration - - beforeEach { - mockStorage = SynchronousStorage( - customWriter: try! DatabaseQueue(), - customMigrationTargets: [ - SNUtilitiesKit.self, - SNSnodeKit.self, - SNMessagingKit.self, - SNUIKit.self - ] - ) - dependencies = Dependencies( - storage: mockStorage, - scheduler: .immediate - ) - mockStorage.write { db in - try SessionThread( - id: "TestId", - variant: .contact - ).insert(db) - } - viewModel = ThreadDisappearingMessagesSettingsViewModel( - threadId: "TestId", - threadVariant: .contact, - config: DisappearingMessagesConfiguration.defaultWith("TestId"), - using: dependencies - ) - cancellables.append( - viewModel.observableTableData - .receive(on: ImmediateScheduler.shared) - .sink( - receiveCompletion: { _ in }, - receiveValue: { viewModel.updateTableData($0.0) } - ) - ) - } - - afterEach { - cancellables.forEach { $0.cancel() } - - mockStorage = nil - cancellables = [] - dependencies = nil - viewModel = nil - } - - // MARK: - Basic Tests - + // MARK: -- has the correct title it("has the correct title") { expect(viewModel.title).to(equal("DISAPPEARING_MESSAGES".localized())) } + // MARK: -- has the correct number of items it("has the correct number of items") { - expect(viewModel.tableData.count) - .to(equal(1)) - expect(viewModel.tableData.first?.elements.count) - .to(equal(12)) + expect(viewModel.tableData.count).to(equal(1)) + expect(viewModel.tableData.first?.elements.count).to(equal(12)) } + // MARK: -- has the correct default state it("has the correct default state") { expect(viewModel.tableData.first?.elements.first) .to( @@ -117,6 +98,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { ) } + // MARK: -- starts with the correct item active if not default it("starts with the correct item active if not default") { let config: DisappearingMessagesConfiguration = DisappearingMessagesConfiguration .defaultWith("TestId") @@ -176,6 +158,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { ) } + // MARK: -- has no right bar button it("has no right bar button") { var items: [ParentType.NavItem]? @@ -191,8 +174,9 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { expect(items).to(equal([])) } + // MARK: -- when changed from the previous setting context("when changed from the previous setting") { - var items: [ParentType.NavItem]? + @TestState var items: [ParentType.NavItem]? beforeEach { cancellables.append( @@ -207,6 +191,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { viewModel.tableData.first?.elements.last?.onTap?() } + // MARK: ---- shows the save button it("shows the save button") { expect(items) .to(equal([ @@ -218,7 +203,9 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { ])) } + // MARK: ---- and saving context("and saving") { + // MARK: ------ dismisses the screen it("dismisses the screen") { var didDismissScreen: Bool = false @@ -236,6 +223,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { expect(didDismissScreen).to(beTrue()) } + // MARK: ------ saves the updated config it("saves the updated config") { items?.first?.action?() @@ -252,3 +240,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { } } } + +// MARK: - Test Types + +fileprivate typealias ParentType = SessionTableViewModel diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index e84e52368..93fb30727 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -11,97 +11,65 @@ import SessionUtilitiesKit @testable import Session class ThreadSettingsViewModelSpec: QuickSpec { - typealias ParentType = SessionTableViewModel - - // MARK: - Spec - - override func spec() { - var mockStorage: Storage! - var mockCaches: MockCaches! - var mockGeneralCache: MockGeneralCache! - var disposables: [AnyCancellable] = [] - var dependencies: Dependencies! - var viewModel: ThreadSettingsViewModel! - var didTriggerSearchCallbackTriggered: Bool = false + override class func spec() { + // MARK: Configuration - describe("a ThreadSettingsViewModel") { - // MARK: - Configuration - - beforeEach { - mockStorage = SynchronousStorage( - customWriter: try! DatabaseQueue(), - customMigrationTargets: [ - SNUtilitiesKit.self, - SNSnodeKit.self, - SNMessagingKit.self, - SNUIKit.self - ] - ) - mockCaches = MockCaches() - mockGeneralCache = MockGeneralCache() - dependencies = Dependencies( - storage: mockStorage, - caches: mockCaches, - scheduler: .immediate - ) - mockCaches[.general] = mockGeneralCache - mockGeneralCache.when { $0.encodedPublicKey }.thenReturn("05\(TestConstants.publicKey)") - mockStorage.write { db in - try SessionThread( - id: "TestId", - variant: .contact - ).insert(db) - - try Identity( - variant: .x25519PublicKey, - data: Data(hex: TestConstants.publicKey) - ).insert(db) - - try Profile( - id: "05\(TestConstants.publicKey)", - name: "TestMe", - lastNameUpdate: 0, - lastProfilePictureUpdate: 0 - ).insert(db) - - try Profile( - id: "TestId", - name: "TestUser", - lastNameUpdate: 0, - lastProfilePictureUpdate: 0 - ).insert(db) - } - viewModel = ThreadSettingsViewModel( - threadId: "TestId", - threadVariant: .contact, - didTriggerSearch: { - didTriggerSearchCallbackTriggered = true - }, - using: dependencies - ) - disposables.append( - viewModel.observableTableData - .receive(on: ImmediateScheduler.shared) - .sink( - receiveCompletion: { _ in }, - receiveValue: { viewModel.updateTableData($0.0) } - ) - ) - } - - afterEach { - disposables.forEach { $0.cancel() } + @TestState var mockStorage: Storage! = SynchronousStorage( + customWriter: try! DatabaseQueue(), + customMigrationTargets: [ + SNUtilitiesKit.self, + SNSnodeKit.self, + SNMessagingKit.self, + SNUIKit.self + ], + initialData: { db in + try Identity( + variant: .x25519PublicKey, + data: Data(hex: TestConstants.publicKey) + ).insert(db) - mockStorage = nil - disposables = [] - dependencies = nil - viewModel = nil - didTriggerSearchCallbackTriggered = false + try SessionThread(id: "TestId",variant: .contact).insert(db) + try Profile(id: "05\(TestConstants.publicKey)", name: "TestMe").insert(db) + try Profile(id: "TestId", name: "TestUser").insert(db) } - - // MARK: - Basic Tests - + ) + @TestState var mockGeneralCache: MockGeneralCache! = MockGeneralCache( + initialSetup: { cache in + cache.when { $0.encodedPublicKey }.thenReturn("05\(TestConstants.publicKey)") + } + ) + @TestState var mockCaches: MockCaches! = MockCaches() + .setting(cache: .general, to: mockGeneralCache) + @TestState var dependencies: Dependencies! = Dependencies( + storage: mockStorage, + caches: mockCaches, + scheduler: .immediate + ) + @TestState var threadVariant: SessionThread.Variant! = .contact + @TestState var didTriggerSearchCallbackTriggered: Bool! = false + @TestState var viewModel: ThreadSettingsViewModel! = ThreadSettingsViewModel( + threadId: "TestId", + threadVariant: .contact, + didTriggerSearch: { + didTriggerSearchCallbackTriggered = true + }, + using: dependencies + ) + + @TestState var disposables: [AnyCancellable]! = [ + viewModel.observableTableData + .receive(on: ImmediateScheduler.shared) + .sink( + receiveCompletion: { _ in }, + receiveValue: { viewModel.updateTableData($0.0) } + ) + ] + + // MARK: - a ThreadSettingsViewModel + describe("a ThreadSettingsViewModel") { + // MARK: -- with any conversation type context("with any conversation type") { + // MARK: ---- triggers the search callback when tapping search it("triggers the search callback when tapping search") { viewModel.tableData .first(where: { $0.model == .content })? @@ -112,6 +80,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { expect(didTriggerSearchCallbackTriggered).to(beTrue()) } + // MARK: ---- mutes a conversation it("mutes a conversation") { viewModel.tableData .first(where: { $0.model == .content })? @@ -127,6 +96,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { .toNot(beNil()) } + // MARK: ---- unmutes a conversation it("unmutes a conversation") { mockStorage.write { db in try SessionThread @@ -158,6 +128,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { } } + // MARK: -- with a note-to-self conversation context("with a note-to-self conversation") { beforeEach { mockStorage.write { db in @@ -187,10 +158,12 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) } + // MARK: ---- has the correct title it("has the correct title") { expect(viewModel.title).to(equal("vc_settings_title".localized())) } + // MARK: ---- starts in the standard nav state it("starts in the standard nav state") { expect(viewModel.navState.firstValue()) .to(equal(.standard)) @@ -206,6 +179,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ])) } + // MARK: ---- has no mute button it("has no mute button") { expect( viewModel.tableData @@ -215,6 +189,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ).to(beNil()) } + // MARK: ---- when entering edit mode context("when entering edit mode") { beforeEach { viewModel.navState.sinkAndStore(in: &disposables) @@ -222,6 +197,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { viewModel.textChanged("TestNew", for: .nickname) } + // MARK: ------ enters the editing state it("enters the editing state") { expect(viewModel.navState.firstValue()) .to(equal(.editing)) @@ -244,11 +220,13 @@ class ThreadSettingsViewModelSpec: QuickSpec { ])) } + // MARK: ------ when cancelling edit mode context("when cancelling edit mode") { beforeEach { viewModel.leftNavItems.firstValue()??.first?.action?() } + // MARK: -------- exits editing mode it("exits editing mode") { expect(viewModel.navState.firstValue()) .to(equal(.standard)) @@ -264,6 +242,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ])) } + // MARK: -------- does not update the nickname for the current user it("does not update the nickname for the current user") { expect( mockStorage @@ -276,11 +255,13 @@ class ThreadSettingsViewModelSpec: QuickSpec { } } + // MARK: ------ when saving edit mode context("when saving edit mode") { beforeEach { viewModel.rightNavItems.firstValue()??.first?.action?() } + // MARK: -------- exits editing mode it("exits editing mode") { expect(viewModel.navState.firstValue()) .to(equal(.standard)) @@ -296,6 +277,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ])) } + // MARK: -------- updates the nickname for the current user it("updates the nickname for the current user") { expect( mockStorage @@ -310,6 +292,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { } } + // MARK: -- with a one-to-one conversation context("with a one-to-one conversation") { beforeEach { mockStorage.write { db in @@ -322,10 +305,12 @@ class ThreadSettingsViewModelSpec: QuickSpec { } } + // MARK: ---- has the correct title it("has the correct title") { expect(viewModel.title).to(equal("vc_settings_title".localized())) } + // MARK: ---- starts in the standard nav state it("starts in the standard nav state") { expect(viewModel.navState.firstValue()) .to(equal(.standard)) @@ -341,6 +326,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ])) } + // MARK: ---- when entering edit mode context("when entering edit mode") { beforeEach { viewModel.navState.sinkAndStore(in: &disposables) @@ -348,6 +334,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { viewModel.textChanged("TestUserNew", for: .nickname) } + // MARK: ------ enters the editing state it("enters the editing state") { expect(viewModel.navState.firstValue()) .to(equal(.editing)) @@ -370,11 +357,13 @@ class ThreadSettingsViewModelSpec: QuickSpec { ])) } + // MARK: ------ when cancelling edit mode context("when cancelling edit mode") { beforeEach { viewModel.leftNavItems.firstValue()??.first?.action?() } + // MARK: -------- exits editing mode it("exits editing mode") { expect(viewModel.navState.firstValue()) .to(equal(.standard)) @@ -390,6 +379,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ])) } + // MARK: -------- does not update the nickname for the current user it("does not update the nickname for the current user") { expect( mockStorage @@ -400,11 +390,13 @@ class ThreadSettingsViewModelSpec: QuickSpec { } } + // MARK: ------ when saving edit mode context("when saving edit mode") { beforeEach { viewModel.rightNavItems.firstValue()??.first?.action?() } + // MARK: -------- exits editing mode it("exits editing mode") { expect(viewModel.navState.firstValue()) .to(equal(.standard)) @@ -420,6 +412,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ])) } + // MARK: -------- updates the nickname for the current user it("updates the nickname for the current user") { expect( mockStorage @@ -432,6 +425,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { } } + // MARK: -- with a group conversation context("with a group conversation") { beforeEach { mockStorage.write { db in @@ -461,10 +455,12 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) } + // MARK: ---- has the correct title it("has the correct title") { expect(viewModel.title).to(equal("vc_group_settings_title".localized())) } + // MARK: ---- starts in the standard nav state it("starts in the standard nav state") { expect(viewModel.navState.firstValue()) .to(equal(.standard)) @@ -474,6 +470,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { } } + // MARK: -- with a community conversation context("with a community conversation") { beforeEach { mockStorage.write { db in @@ -503,10 +500,12 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) } + // MARK: ---- has the correct title it("has the correct title") { expect(viewModel.title).to(equal("vc_group_settings_title".localized())) } + // MARK: ---- starts in the standard nav state it("starts in the standard nav state") { expect(viewModel.navState.firstValue()) .to(equal(.standard)) @@ -518,3 +517,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { } } } + +// MARK: - Test Types + +fileprivate typealias ParentType = SessionTableViewModel diff --git a/SessionTests/Settings/NotificationContentViewModelSpec.swift b/SessionTests/Settings/NotificationContentViewModelSpec.swift index 090ea3d61..6925a22ee 100644 --- a/SessionTests/Settings/NotificationContentViewModelSpec.swift +++ b/SessionTests/Settings/NotificationContentViewModelSpec.swift @@ -11,52 +11,38 @@ import SessionUtilitiesKit @testable import Session class NotificationContentViewModelSpec: QuickSpec { - // MARK: - Spec - - override func spec() { - var mockStorage: Storage! - var dataChangeCancellable: AnyCancellable? - var dismissCancellable: AnyCancellable? - var viewModel: NotificationContentViewModel! + override class func spec() { + // MARK: Configuration + @TestState var mockStorage: Storage! = SynchronousStorage( + customWriter: try! DatabaseQueue(), + customMigrationTargets: [ + SNUtilitiesKit.self, + SNSnodeKit.self, + SNMessagingKit.self, + SNUIKit.self + ] + ) + @TestState var viewModel: NotificationContentViewModel! = NotificationContentViewModel( + storage: mockStorage, + scheduling: .immediate + ) + @TestState var dataChangeCancellable: AnyCancellable? = viewModel.observableTableData + .receive(on: ImmediateScheduler.shared) + .sink( + receiveCompletion: { _ in }, + receiveValue: { viewModel.updateTableData($0.0) } + ) + @TestState var dismissCancellable: AnyCancellable? + + // MARK: - a NotificationContentViewModel describe("a NotificationContentViewModel") { - // MARK: - Configuration - - beforeEach { - mockStorage = SynchronousStorage( - customWriter: try! DatabaseQueue(), - customMigrationTargets: [ - SNUtilitiesKit.self, - SNSnodeKit.self, - SNMessagingKit.self, - SNUIKit.self - ] - ) - viewModel = NotificationContentViewModel(storage: mockStorage, scheduling: .immediate) - dataChangeCancellable = viewModel.observableTableData - .receive(on: ImmediateScheduler.shared) - .sink( - receiveCompletion: { _ in }, - receiveValue: { viewModel.updateTableData($0.0) } - ) - } - - afterEach { - dataChangeCancellable?.cancel() - dismissCancellable?.cancel() - - mockStorage = nil - dataChangeCancellable = nil - dismissCancellable = nil - viewModel = nil - } - - // MARK: - Basic Tests - + // MARK: -- has the correct title it("has the correct title") { expect(viewModel.title).to(equal("NOTIFICATIONS_STYLE_CONTENT_TITLE".localized())) } + // MARK: -- has the correct number of items it("has the correct number of items") { expect(viewModel.tableData.count) .to(equal(1)) @@ -64,6 +50,7 @@ class NotificationContentViewModelSpec: QuickSpec { .to(equal(3)) } + // MARK: -- has the correct default state it("has the correct default state") { expect(viewModel.tableData.first?.elements) .to( @@ -96,6 +83,7 @@ class NotificationContentViewModelSpec: QuickSpec { ) } + // MARK: -- starts with the correct item active if not default it("starts with the correct item active if not default") { mockStorage.write { db in db[.preferencesNotificationPreviewType] = Preferences.NotificationPreviewType.nameNoPreview @@ -139,7 +127,9 @@ class NotificationContentViewModelSpec: QuickSpec { ) } + // MARK: -- when tapping an item context("when tapping an item") { + // MARK: ---- updates the saved preference it("updates the saved preference") { viewModel.tableData.first?.elements.last?.onTap?() @@ -147,6 +137,7 @@ class NotificationContentViewModelSpec: QuickSpec { .to(equal(Preferences.NotificationPreviewType.noNameNoPreview)) } + // MARK: ---- dismisses the screen it("dismisses the screen") { var didDismissScreen: Bool = false diff --git a/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift b/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift index 3a93e06a9..d255064d5 100644 --- a/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift +++ b/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift @@ -9,21 +9,19 @@ import Nimble @testable import SessionUtilitiesKit class IdentitySpec: QuickSpec { - // MARK: - Spec - - override func spec() { - var mockStorage: Storage! + override class func spec() { + // MARK: Configuration + @TestState var mockStorage: Storage! = SynchronousStorage( + customWriter: try! DatabaseQueue(), + customMigrationTargets: [ + SNUtilitiesKit.self + ] + ) + + // MARK: - an Identity describe("an Identity") { - beforeEach { - mockStorage = SynchronousStorage( - customWriter: try! DatabaseQueue(), - customMigrationTargets: [ - SNUtilitiesKit.self - ] - ) - } - + // MARK: -- correctly retrieves the user user public key it("correctly retrieves the user user public key") { mockStorage.write { db in try Identity(variant: .x25519PublicKey, data: "Test1".data(using: .utf8)!).insert(db) @@ -35,6 +33,7 @@ class IdentitySpec: QuickSpec { } } + // MARK: -- correctly retrieves the user private key it("correctly retrieves the user private key") { mockStorage.write { db in try Identity(variant: .x25519PrivateKey, data: "Test2".data(using: .utf8)!).insert(db) @@ -46,6 +45,7 @@ class IdentitySpec: QuickSpec { } } + // MARK: -- correctly retrieves the user key pair it("correctly retrieves the user key pair") { mockStorage.write { db in try Identity(variant: .x25519PublicKey, data: "Test3".data(using: .utf8)!).insert(db) @@ -62,6 +62,7 @@ class IdentitySpec: QuickSpec { } } + // MARK: -- correctly determines if the user exists it("correctly determines if the user exists") { mockStorage.write { db in try Identity(variant: .x25519PublicKey, data: "Test3".data(using: .utf8)!).insert(db) @@ -74,6 +75,7 @@ class IdentitySpec: QuickSpec { } } + // MARK: -- correctly retrieves the user ED25519 key pair it("correctly retrieves the user ED25519 key pair") { mockStorage.write { db in try Identity(variant: .ed25519PublicKey, data: "Test5".data(using: .utf8)!).insert(db) @@ -90,6 +92,7 @@ class IdentitySpec: QuickSpec { } } + // MARK: -- correctly retrieves the hex encoded seed it("correctly retrieves the hex encoded seed") { mockStorage.write { db in try Identity(variant: .seed, data: "Test7".data(using: .utf8)!).insert(db) diff --git a/SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift b/SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift index 6b98ed4f4..2886f9db4 100644 --- a/SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift +++ b/SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift @@ -9,113 +9,22 @@ import Nimble @testable import SessionUtilitiesKit class PersistableRecordUtilitiesSpec: QuickSpec { - struct TestType: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { - public static var databaseTableName: String { "TestType" } + override class func spec() { + // MARK: Configuration - public typealias Columns = CodingKeys - public enum CodingKeys: String, CodingKey, ColumnExpression { - case columnA - case columnB - } - - public let columnA: String - public let columnB: String? - } - - struct MutableTestType: Codable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible { - public static var databaseTableName: String { "MutableTestType" } - - public typealias Columns = CodingKeys - public enum CodingKeys: String, CodingKey, ColumnExpression { - case id - case columnA - case columnB - } - - public var id: Int64? - public let columnA: String - public let columnB: String? - - init(id: Int64? = nil, columnA: String, columnB: String?) { - self.id = id - self.columnA = columnA - self.columnB = columnB - } - - mutating func didInsert(_ inserted: InsertionSuccess) { - self.id = inserted.rowID - } - } - - enum TestInsertTestTypeMigration: Migration { - static let target: TargetMigrations.Identifier = .test - static let identifier: String = "TestInsertTestType" - static let needsConfigSync: Bool = false - static let minExpectedRunDuration: TimeInterval = 0 - - static func migrate(_ db: Database) throws { - try db.create(table: TestType.self) { t in - t.column(.columnA, .text).primaryKey() - } - - try db.create(table: MutableTestType.self) { t in - t.column(.id, .integer).primaryKey(autoincrement: true) - t.column(.columnA, .text).unique() - } - } - } - - enum TestAddColumnMigration: Migration { - static let target: TargetMigrations.Identifier = .test - static let identifier: String = "TestAddColumn" - static let needsConfigSync: Bool = false - static let minExpectedRunDuration: TimeInterval = 0 - - static func migrate(_ db: Database) throws { - try db.alter(table: TestType.self) { t in - t.add(.columnB, .text) - } - - try db.alter(table: MutableTestType.self) { t in - t.add(.columnB, .text) - } - } - } - - private struct TestTarget: MigratableTarget { - static func migrations(_ db: Database) -> TargetMigrations { - return TargetMigrations( - identifier: .test, - migrations: (0..<100) - .map { _ in [] } - .appending([TestInsertTestTypeMigration.self]) - ) - } - } - - // MARK: - Spec - - override func spec() { - var customWriter: DatabaseQueue! - var mockStorage: Storage! + @TestState var customWriter: DatabaseQueue! = try! DatabaseQueue() + @TestState var mockStorage: Storage! = SynchronousStorage( + customWriter: customWriter, + customMigrationTargets: [ + TestTarget.self + ] + ) + // MARK: - a PersistableRecord describe("a PersistableRecord") { - beforeEach { - customWriter = try! DatabaseQueue() - mockStorage = SynchronousStorage( - customWriter: customWriter, - customMigrationTargets: [ - TestTarget.self - ] - ) - } - - afterEach { - customWriter = nil - mockStorage = nil - } - + // MARK: -- before running the add column migration context("before running the add column migration") { + // MARK: ---- fails when using the standard insert it("fails when using the standard insert") { mockStorage.write { db in expect { @@ -125,6 +34,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- fails when using the standard inserted it("fails when using the standard inserted") { mockStorage.write { db in expect { @@ -134,6 +44,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- fails when using the standard save and the item does not already exist it("fails when using the standard save and the item does not already exist") { mockStorage.write { db in expect { @@ -143,6 +54,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- fails when using the standard saved and the item does not already exist it("fails when using the standard saved and the item does not already exist") { mockStorage.write { db in expect { @@ -152,6 +64,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- fails when using the standard upsert and the item does not already exist it("fails when using the standard upsert and the item does not already exist") { mockStorage.write { db in expect { @@ -161,6 +74,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- fails when using the standard mutable upsert and the item does not already exist it("fails when using the standard mutable upsert and the item does not already exist") { mockStorage.write { db in expect { @@ -172,6 +86,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- fails when using the standard upsert and the item already exists it("fails when using the standard upsert and the item already exists") { mockStorage.write { db in expect { @@ -185,6 +100,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- fails when using the standard mutable upsert and the item already exists it("fails when using the standard mutable upsert and the item already exists") { mockStorage.write { db in expect { @@ -200,6 +116,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe insert it("succeeds when using the migration safe insert") { mockStorage.write { db in expect { @@ -214,6 +131,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe inserted it("succeeds when using the migration safe inserted") { mockStorage.write { db in expect { @@ -235,6 +153,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe save and the item does not already exist it("succeeds when using the migration safe save and the item does not already exist") { mockStorage.write { db in expect { @@ -244,6 +163,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe saved and the item does not already exist it("succeeds when using the migration safe saved and the item does not already exist") { mockStorage.write { db in expect { @@ -265,6 +185,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe upsert and the item does not already exist it("succeeds when using the migration safe upsert and the item does not already exist") { mockStorage.write { db in expect { @@ -274,6 +195,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe mutable upsert and the item does not already exist it("succeeds when using the migration safe mutable upsert and the item does not already exist") { mockStorage.write { db in expect { @@ -290,8 +212,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } - // Note: The built-in 'update' method only updates existing columns so this shouldn't fail + // MARK: ---- succeeds when using the standard save and the item already exists it("succeeds when using the standard save and the item already exists") { + /// **Note:** The built-in 'update' method only updates existing columns so this shouldn't fail mockStorage.write { db in expect { try db.execute( @@ -304,10 +227,10 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } - // Note: The built-in 'update' method only updates existing columns so this won't fail - // due to the structure discrepancy but won't update the id as that only happens on - // insert + // MARK: ---- succeeds when using the standard saved and the item already exists it("succeeds when using the standard saved and the item already exists") { + /// **Note:** The built-in 'update' method only updates existing columns so this won't fail + /// due to the structure discrepancy but won't update the id as that only happens on insert mockStorage.write { db in expect { try db.execute( @@ -339,6 +262,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: -- after running the add column migration context("after running the add column migration") { beforeEach { var migrator: DatabaseMigrator = DatabaseMigrator() @@ -352,6 +276,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { .toNot(throwError()) } + // MARK: ---- succeeds when using the standard insert it("succeeds when using the standard insert") { mockStorage.write { db in expect { @@ -366,6 +291,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the standard inserted it("succeeds when using the standard inserted") { mockStorage.write { db in expect { @@ -380,6 +306,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the standard save and the item does not already exist it("succeeds when using the standard save and the item does not already exist") { mockStorage.write { db in expect { @@ -389,6 +316,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the standard saved and the item does not already exist it("succeeds when using the standard saved and the item does not already exist") { mockStorage.write { db in expect { @@ -398,6 +326,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the standard save and the item already exists it("succeeds when using the standard save and the item already exists") { mockStorage.write { db in expect { @@ -411,9 +340,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } - // Note: The built-in 'update' method won't update the id as that only happens on - // insert + // MARK: ---- succeeds when using the standard saved and the item already exists it("succeeds when using the standard saved and the item already exists") { + /// **Note:** The built-in 'update' method won't update the id as that only happens on insert mockStorage.write { db in expect { try db.execute( @@ -444,6 +373,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the standard upsert and the item does not already exist it("succeeds when using the standard upsert and the item does not already exist") { mockStorage.write { db in expect { @@ -453,6 +383,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the standard mutable upsert and the item does not already exist it("succeeds when using the standard mutable upsert and the item does not already exist") { mockStorage.write { db in expect { @@ -464,6 +395,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the standard upsert and the item already exists it("succeeds when using the standard upsert and the item already exists") { mockStorage.write { db in expect { @@ -477,9 +409,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } - // Note: The built-in 'update' method won't update the id as that only happens on - // insert + // MARK: ---- succeeds when using the standard mutable upsert and the item already exists it("succeeds when using the standard mutable upsert and the item already exists") { + /// **Note:** The built-in 'update' method won't update the id as that only happens on insert mockStorage.write { db in expect { try db.execute( @@ -512,6 +444,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe insert it("succeeds when using the migration safe insert") { mockStorage.write { db in expect { @@ -526,6 +459,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe inserted it("succeeds when using the migration safe inserted") { mockStorage.write { db in expect { @@ -547,6 +481,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe save and the item does not already exist it("succeeds when using the migration safe save and the item does not already exist") { mockStorage.write { db in expect { @@ -556,6 +491,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe saved and the item does not already exist it("succeeds when using the migration safe saved and the item does not already exist") { mockStorage.write { db in expect { @@ -565,6 +501,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe save and the item already exists it("succeeds when using the migration safe save and the item already exists") { mockStorage.write { db in expect { @@ -578,9 +515,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } - // Note: The built-in 'update' method won't update the id as that only happens on - // insert + // MARK: ---- succeeds when using the migration safe saved and the item already exists it("succeeds when using the migration safe saved and the item already exists") { + /// **Note:** The built-in 'update' method won't update the id as that only happens on insert mockStorage.write { db in expect { try db.execute( @@ -612,6 +549,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe upsert and the item does not already exist it("succeeds when using the migration safe upsert and the item does not already exist") { mockStorage.write { db in expect { @@ -621,6 +559,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe mutable upsert and the item does not already exist it("succeeds when using the migration safe mutable upsert and the item does not already exist") { mockStorage.write { db in expect { @@ -632,6 +571,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + // MARK: ---- succeeds when using the migration safe upsert and the item already exists it("succeeds when using the migration safe upsert and the item already exists") { mockStorage.write { db in expect { @@ -645,9 +585,9 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } - // Note: The built-in 'update' method won't update the id as that only happens on - // insert + // MARK: ---- succeeds when using the migration safe mutable upsert and the item already exists it("succeeds when using the migration safe mutable upsert and the item already exists") { + /// **Note:** The built-in 'update' method won't update the id as that only happens on insert mockStorage.write { db in expect { try db.execute( @@ -683,3 +623,89 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } } + +// MARK: - Test Types + +fileprivate struct TestType: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { + public static var databaseTableName: String { "TestType" } + + public typealias Columns = CodingKeys + public enum CodingKeys: String, CodingKey, ColumnExpression { + case columnA + case columnB + } + + public let columnA: String + public let columnB: String? +} + +fileprivate struct MutableTestType: Codable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible { + public static var databaseTableName: String { "MutableTestType" } + + public typealias Columns = CodingKeys + public enum CodingKeys: String, CodingKey, ColumnExpression { + case id + case columnA + case columnB + } + + public var id: Int64? + public let columnA: String + public let columnB: String? + + init(id: Int64? = nil, columnA: String, columnB: String?) { + self.id = id + self.columnA = columnA + self.columnB = columnB + } + + mutating func didInsert(_ inserted: InsertionSuccess) { + self.id = inserted.rowID + } +} + +fileprivate enum TestInsertTestTypeMigration: Migration { + static let target: TargetMigrations.Identifier = .test + static let identifier: String = "TestInsertTestType" + static let needsConfigSync: Bool = false + static let minExpectedRunDuration: TimeInterval = 0 + + static func migrate(_ db: Database) throws { + try db.create(table: TestType.self) { t in + t.column(.columnA, .text).primaryKey() + } + + try db.create(table: MutableTestType.self) { t in + t.column(.id, .integer).primaryKey(autoincrement: true) + t.column(.columnA, .text).unique() + } + } +} + +fileprivate enum TestAddColumnMigration: Migration { + static let target: TargetMigrations.Identifier = .test + static let identifier: String = "TestAddColumn" + static let needsConfigSync: Bool = false + static let minExpectedRunDuration: TimeInterval = 0 + + static func migrate(_ db: Database) throws { + try db.alter(table: TestType.self) { t in + t.add(.columnB, .text) + } + + try db.alter(table: MutableTestType.self) { t in + t.add(.columnB, .text) + } + } +} + +fileprivate struct TestTarget: MigratableTarget { + static func migrations(_ db: Database) -> TargetMigrations { + return TargetMigrations( + identifier: .test, + migrations: (0..<100) + .map { _ in [] } + .appending([TestInsertTestTypeMigration.self]) + ) + } +} diff --git a/SessionUtilitiesKitTests/General/ArrayUtilitiesSpec.swift b/SessionUtilitiesKitTests/General/ArrayUtilitiesSpec.swift index b2d9b74dd..701fbb034 100644 --- a/SessionUtilitiesKitTests/General/ArrayUtilitiesSpec.swift +++ b/SessionUtilitiesKitTests/General/ArrayUtilitiesSpec.swift @@ -8,16 +8,12 @@ import Nimble @testable import SessionUtilitiesKit class ArrayUtilitiesSpec: QuickSpec { - private struct TestType: Equatable { - let stringValue: String - let intValue: Int - } - - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - an Array describe("an Array") { + // MARK: -- when grouping context("when grouping") { + // MARK: ---- maintains the original array ordering it("maintains the original array ordering") { let data: [TestType] = [ TestType(stringValue: "b", intValue: 5), @@ -84,3 +80,10 @@ class ArrayUtilitiesSpec: QuickSpec { } } } + +// MARK: - Test Types + +fileprivate struct TestType: Equatable { + let stringValue: String + let intValue: Int +} diff --git a/SessionUtilitiesKitTests/General/DependenciesSpec.swift b/SessionUtilitiesKitTests/General/DependenciesSpec.swift index 60b0d0ed3..aa8c89386 100644 --- a/SessionUtilitiesKitTests/General/DependenciesSpec.swift +++ b/SessionUtilitiesKitTests/General/DependenciesSpec.swift @@ -8,17 +8,16 @@ import Nimble @testable import SessionUtilitiesKit class DependenciesSpec: QuickSpec { - // MARK: - Spec - - override func spec() { - var dependencies: Dependencies! + override class func spec() { + // MARK: Configuration + @TestState var dependencies: Dependencies! = Dependencies() + + // MARK: - Dependencies describe("Dependencies") { - beforeEach { - dependencies = Dependencies() - } - + // MARK: -- when accessing dateNow context("when accessing dateNow") { + // MARK: ---- creates a new date every time when not overwritten it("creates a new date every time when not overwritten") { let date1 = dependencies.dateNow Thread.sleep(forTimeInterval: 0.05) @@ -27,6 +26,7 @@ class DependenciesSpec: QuickSpec { expect(date1.timeIntervalSince1970).toNot(equal(date2.timeIntervalSince1970)) } + // MARK: ---- returns the same new date every time when overwritten it("returns the same new date every time when overwritten") { dependencies.dateNow = Date(timeIntervalSince1970: 1234567890) diff --git a/SessionUtilitiesKitTests/General/SessionIdSpec.swift b/SessionUtilitiesKitTests/General/SessionIdSpec.swift index 62a76e33c..8f4896546 100644 --- a/SessionUtilitiesKitTests/General/SessionIdSpec.swift +++ b/SessionUtilitiesKitTests/General/SessionIdSpec.swift @@ -8,12 +8,14 @@ import Nimble @testable import SessionUtilitiesKit class SessionIdSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - a SessionId describe("a SessionId") { + // MARK: -- when initializing context("when initializing") { + // MARK: ---- with an idString context("with an idString") { + // MARK: ------ succeeds when correct it("succeeds when correct") { let sessionId: SessionId? = SessionId(from: "05\(TestConstants.publicKey)") @@ -21,16 +23,20 @@ class SessionIdSpec: QuickSpec { expect(sessionId?.publicKey).to(equal(TestConstants.publicKey)) } + // MARK: ------ fails when too short it("fails when too short") { expect(SessionId(from: "")).to(beNil()) } + // MARK: ------ fails with an invalid prefix it("fails with an invalid prefix") { expect(SessionId(from: "AB\(TestConstants.publicKey)")).to(beNil()) } } + // MARK: ---- with a prefix and publicKey context("with a prefix and publicKey") { + // MARK: ------ converts the bytes into a hex string it("converts the bytes into a hex string") { let sessionId: SessionId? = SessionId(.standard, publicKey: [0, 1, 2, 3, 4, 5, 6, 7, 8]) @@ -40,6 +46,7 @@ class SessionIdSpec: QuickSpec { } } + // MARK: -- generates the correct hex string it("generates the correct hex string") { expect(SessionId(.unblinded, publicKey: Data(hex: TestConstants.publicKey).bytes).hexString) .to(equal("0088672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) @@ -52,9 +59,13 @@ class SessionIdSpec: QuickSpec { } } + // MARK: - a SessionId Prefix describe("a SessionId Prefix") { + // MARK: -- when initializing context("when initializing") { + // MARK: ---- with just a prefix context("with just a prefix") { + // MARK: ------ succeeds when valid it("succeeds when valid") { expect(SessionId.Prefix(from: "00")).to(equal(.unblinded)) expect(SessionId.Prefix(from: "05")).to(equal(.standard)) @@ -62,24 +73,30 @@ class SessionIdSpec: QuickSpec { expect(SessionId.Prefix(from: "25")).to(equal(.blinded25)) } + // MARK: ------ fails when nil it("fails when nil") { expect(SessionId.Prefix(from: nil)).to(beNil()) } + // MARK: ------ fails when invalid it("fails when invalid") { expect(SessionId.Prefix(from: "AB")).to(beNil()) } } + // MARK: ---- with a longer string context("with a longer string") { + // MARK: ------ fails with invalid hex it("fails with invalid hex") { expect(SessionId.Prefix(from: "Hello!!!")).to(beNil()) } + // MARK: ------ fails with the wrong length it("fails with the wrong length") { expect(SessionId.Prefix(from: String(TestConstants.publicKey.prefix(10)))).to(beNil()) } + // MARK: ------ fails with an invalid prefix it("fails with an invalid prefix") { expect(SessionId.Prefix(from: "AB\(TestConstants.publicKey)")).to(beNil()) } diff --git a/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift b/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift index fff8e4c15..c0c264471 100644 --- a/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift +++ b/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift @@ -9,165 +9,71 @@ import Nimble @testable import SessionUtilitiesKit class JobRunnerSpec: QuickSpec { - struct TestDetails: Codable { - enum ResultType: Codable { - case success - case failure - case permanentFailure - case deferred - } + override class func spec() { + // MARK: Configuration - public let result: ResultType - public let completeTime: Int - public let intValue: Int64 - public let stringValue: String - - init( - result: ResultType = .success, - completeTime: Int = 0, - intValue: Int64 = 100, - stringValue: String = "200" - ) { - self.result = result - self.completeTime = completeTime - self.intValue = intValue - self.stringValue = stringValue - } - } - - struct InvalidDetails: Codable { - func encode(to encoder: Encoder) throws { throw HTTPError.parsingFailed } - } - - enum TestJob: JobExecutor { - static let maxFailureCount: Int = 1 - static let requiresThreadId: Bool = false - static let requiresInteractionId: Bool = false - - static func run( - _ job: Job, - queue: DispatchQueue, - success: @escaping (Job, Bool, Dependencies) -> (), - failure: @escaping (Job, Error?, Bool, Dependencies) -> (), - deferred: @escaping (Job, Dependencies) -> (), - using dependencies: Dependencies - ) { - guard - let detailsData: Data = job.details, - let details: TestDetails = try? JSONDecoder().decode(TestDetails.self, from: detailsData) - else { return success(job, true, dependencies) } - - let completeJob: () -> () = { - // Need to increase the 'completeTime' and 'nextRunTimestamp' to prevent the job - // from immediately being run again or immediately completing afterwards - let updatedJob: Job = job - .with(nextRunTimestamp: TimeInterval(details.completeTime + 1)) - .with( - details: TestDetails( - result: details.result, - completeTime: (details.completeTime + 2), - intValue: details.intValue, - stringValue: details.stringValue - ) - )! - dependencies.storage.write { db in try _ = updatedJob.saved(db) } - - switch details.result { - case .success: success(job, true, dependencies) - case .failure: failure(job, nil, false, dependencies) - case .permanentFailure: failure(job, nil, true, dependencies) - case .deferred: deferred(updatedJob, dependencies) - } - } - - guard dependencies.fixedTime < details.completeTime else { - return queue.async(using: dependencies) { - completeJob() - } - } - - dependencies.asyncExecutions.appendTo(details.completeTime) { - queue.async(using: dependencies) { - completeJob() - } - } - } - } - - // MARK: - Spec - - override func spec() { - var jobRunner: JobRunnerType! - var job1: Job! - var job2: Job! - var mockStorage: Storage! - var dependencies: Dependencies! - - describe("a JobRunner") { - // MARK: - Configuration - - beforeEach { - mockStorage = SynchronousStorage( - customWriter: try! DatabaseQueue(), - customMigrationTargets: [ - SNUtilitiesKit.self - ] - ) - dependencies = Dependencies( - storage: mockStorage, - dateNow: Date(timeIntervalSince1970: 0), - forceSynchronous: true - ) - + @TestState var job1: Job! = Job( + id: 100, + failureCount: 0, + variant: .messageSend, + behaviour: .runOnce, + shouldBlock: false, + shouldSkipLaunchBecomeActive: false, + nextRunTimestamp: 0, + threadId: nil, + interactionId: nil, + details: nil + ) + @TestState var job2: Job! = Job( + id: 101, + failureCount: 0, + variant: .attachmentUpload, + behaviour: .runOnce, + shouldBlock: false, + shouldSkipLaunchBecomeActive: false, + nextRunTimestamp: 0, + threadId: nil, + interactionId: nil, + details: nil + ) + @TestState var mockStorage: Storage! = SynchronousStorage( + customWriter: try! DatabaseQueue(), + customMigrationTargets: [ + SNUtilitiesKit.self + ], + initialData: { db in // Migrations add jobs which we don't want so delete them - mockStorage.write { db in try Job.deleteAll(db) } - - job1 = Job( - id: 100, - failureCount: 0, - variant: .messageSend, - behaviour: .runOnce, - shouldBlock: false, - shouldSkipLaunchBecomeActive: false, - nextRunTimestamp: 0, - threadId: nil, - interactionId: nil, - details: nil - ) - job2 = Job( - id: 101, - failureCount: 0, - variant: .attachmentUpload, - behaviour: .runOnce, - shouldBlock: false, - shouldSkipLaunchBecomeActive: false, - nextRunTimestamp: 0, - threadId: nil, - interactionId: nil, - details: nil - ) - - jobRunner = JobRunner(isTestingJobRunner: true, using: dependencies) - jobRunner.setExecutor(TestJob.self, for: .messageSend) - jobRunner.setExecutor(TestJob.self, for: .attachmentUpload) - jobRunner.setExecutor(TestJob.self, for: .messageReceive) - - // Need to assign this to ensure it's used by nested dependencies - dependencies.jobRunner = jobRunner + try Job.deleteAll(db) } + ) + @TestState var dependencies: Dependencies! = Dependencies( + storage: mockStorage, + dateNow: Date(timeIntervalSince1970: 0), + forceSynchronous: true + ) + @TestState var jobRunner: JobRunnerType! = { + let result = JobRunner(isTestingJobRunner: true, using: dependencies) + result.setExecutor(TestJob.self, for: .messageSend) + result.setExecutor(TestJob.self, for: .attachmentUpload) + result.setExecutor(TestJob.self, for: .messageReceive) + // Need to assign this to ensure it's used by nested dependencies + dependencies.jobRunner = result + + return result + }() + + // MARK: - a JobRunner + describe("a JobRunner") { afterEach { /// We **must** set `fixedTime` to ensure we break any loops within the `TestJob` executor dependencies.fixedTime = Int.max jobRunner.stopAndClearPendingJobs() - jobRunner = nil - mockStorage = nil - dependencies = nil } - // MARK: - when configuring + // MARK: -- when configuring context("when configuring") { - // MARK: -- adds an executor correctly + // MARK: ---- adds an executor correctly it("adds an executor correctly") { job1 = Job( id: 101, @@ -222,28 +128,28 @@ class JobRunnerSpec: QuickSpec { } } - // MARK: -- when managing state + // MARK: ---- when managing state context("when managing state") { - // MARK: ---- by checking if a job is currently running + // MARK: ------ by checking if a job is currently running context("by checking if a job is currently running") { - // MARK: ------ returns false when not given a job + // MARK: -------- returns false when not given a job it("returns false when not given a job") { expect(jobRunner.isCurrentlyRunning(nil)).to(beFalse()) } - // MARK: ------ returns false when given a job that has not been persisted + // MARK: -------- returns false when given a job that has not been persisted it("returns false when given a job that has not been persisted") { job1 = Job(variant: .messageSend) expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse()) } - // MARK: ------ returns false when given a job that is not running + // MARK: -------- returns false when given a job that is not running it("returns false when given a job that is not running") { expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse()) } - // MARK: ------ returns true when given a non blocking job that is running + // MARK: -------- returns true when given a non blocking job that is running it("returns true when given a non blocking job that is running") { job1 = job1.with(details: TestDetails(completeTime: 1)) jobRunner.appDidFinishLaunching(using: dependencies) @@ -261,7 +167,7 @@ class JobRunnerSpec: QuickSpec { expect(jobRunner.isCurrentlyRunning(job1)).to(beTrue()) } - // MARK: ------ returns true when given a blocking job that is running + // MARK: -------- returns true when given a blocking job that is running it("returns true when given a blocking job that is running") { job2 = Job( id: 101, @@ -293,14 +199,14 @@ class JobRunnerSpec: QuickSpec { } } - // MARK: ---- by getting the details for jobs + // MARK: ------ by getting the details for jobs context("by getting the details for jobs") { - // MARK: ------ returns an empty dictionary when there are no jobs + // MARK: -------- returns an empty dictionary when there are no jobs it("returns an empty dictionary when there are no jobs") { expect(jobRunner.allJobInfo()).to(equal([:])) } - // MARK: ------ returns an empty dictionary when there are no jobs matching the filters + // MARK: -------- returns an empty dictionary when there are no jobs matching the filters it("returns an empty dictionary when there are no jobs matching the filters") { jobRunner.appDidFinishLaunching(using: dependencies) jobRunner.appDidBecomeActive(using: dependencies) @@ -317,7 +223,7 @@ class JobRunnerSpec: QuickSpec { expect(jobRunner.jobInfoFor(state: .running, variant: .messageSend)).to(equal([:])) } - // MARK: ------ can filter to specific jobs + // MARK: -------- can filter to specific jobs it("can filter to specific jobs") { job1 = Job( id: 100, @@ -386,7 +292,7 @@ class JobRunnerSpec: QuickSpec { ])) } - // MARK: ------ can filter to running jobs + // MARK: -------- can filter to running jobs it("can filter to running jobs") { job1 = Job( id: 100, @@ -450,7 +356,7 @@ class JobRunnerSpec: QuickSpec { expect(Array(jobRunner.allJobInfo().keys).sorted()).to(equal([100, 101])) } - // MARK: ------ can filter to pending jobs + // MARK: -------- can filter to pending jobs it("can filter to pending jobs") { job1 = Job( id: 100, @@ -514,7 +420,7 @@ class JobRunnerSpec: QuickSpec { expect(Array(jobRunner.allJobInfo().keys).sorted()).to(equal([100, 101])) } - // MARK: ------ can filter to specific variants + // MARK: -------- can filter to specific variants it("can filter to specific variants") { job1 = job1.with(details: TestDetails(completeTime: 1)) job2 = job2.with(details: TestDetails(completeTime: 2)) @@ -552,7 +458,7 @@ class JobRunnerSpec: QuickSpec { expect(Array(jobRunner.allJobInfo().keys).sorted()).to(equal([100, 101])) } - // MARK: ------ includes non blocking jobs + // MARK: -------- includes non blocking jobs it("includes non blocking jobs") { job2 = job2.with(details: TestDetails(completeTime: 1)) jobRunner.appDidFinishLaunching(using: dependencies) @@ -580,7 +486,7 @@ class JobRunnerSpec: QuickSpec { ])) } - // MARK: ------ includes blocking jobs + // MARK: -------- includes blocking jobs it("includes blocking jobs") { job2 = Job( id: 101, @@ -622,9 +528,9 @@ class JobRunnerSpec: QuickSpec { } } - // MARK: ---- by checking for an existing job + // MARK: ------ by checking for an existing job context("by checking for an existing job") { - // MARK: ------ returns false for a queue that doesn't exist + // MARK: -------- returns false for a queue that doesn't exist it("returns false for a queue that doesn't exist") { jobRunner = JobRunner( isTestingJobRunner: true, @@ -636,19 +542,19 @@ class JobRunnerSpec: QuickSpec { .to(beFalse()) } - // MARK: ------ returns false when the provided details fail to decode + // MARK: -------- returns false when the provided details fail to decode it("returns false when the provided details fail to decode") { expect(jobRunner.hasJob(of: .attachmentUpload, with: InvalidDetails())) .to(beFalse()) } - // MARK: ------ returns false when there is not a pending or running job + // MARK: -------- returns false when there is not a pending or running job it("returns false when there is not a pending or running job") { expect(jobRunner.hasJob(of: .attachmentUpload, with: TestDetails())) .to(beFalse()) } - // MARK: ------ returns true when there is a pending job + // MARK: -------- returns true when there is a pending job it("returns true when there is a pending job") { job1 = Job( id: 100, @@ -703,7 +609,7 @@ class JobRunnerSpec: QuickSpec { .to(beTrue()) } - // MARK: ------ returns true when there is a running job + // MARK: -------- returns true when there is a running job it("returns true when there is a running job") { job2 = job2.with(details: TestDetails(completeTime: 1)) jobRunner.appDidFinishLaunching(using: dependencies) @@ -724,7 +630,7 @@ class JobRunnerSpec: QuickSpec { .to(beTrue()) } - // MARK: ------ returns true when there is a blocking job + // MARK: -------- returns true when there is a blocking job it("returns true when there is a blocking job") { job2 = Job( id: 101, @@ -760,7 +666,7 @@ class JobRunnerSpec: QuickSpec { .to(beTrue()) } - // MARK: ------ returns true when there is a non blocking job + // MARK: -------- returns true when there is a non blocking job it("returns true when there is a non blocking job") { job2 = job2.with(details: TestDetails(completeTime: 1)) jobRunner.appDidFinishLaunching(using: dependencies) @@ -782,9 +688,9 @@ class JobRunnerSpec: QuickSpec { } } - // MARK: ---- by being notified of app launch + // MARK: ------ by being notified of app launch context("by being notified of app launch") { - // MARK: ------ does not start a job before getting the app launch call + // MARK: -------- does not start a job before getting the app launch call it("does not start a job before getting the app launch call") { job1 = job1.with(details: TestDetails(completeTime: 1)) @@ -800,7 +706,7 @@ class JobRunnerSpec: QuickSpec { expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse()) } - // MARK: ------ starts the job queues if there are no app launch jobs + // MARK: -------- starts the job queues if there are no app launch jobs it("does nothing if there are no app launch jobs") { job1 = job1.with(details: TestDetails(completeTime: 1)) @@ -818,9 +724,9 @@ class JobRunnerSpec: QuickSpec { } } - // MARK: ---- by being notified of app becoming active + // MARK: ------ by being notified of app becoming active context("by being notified of app becoming active") { - // MARK: ------ does not start a job before getting the app active call + // MARK: -------- does not start a job before getting the app active call it("does not start a job before getting the app active call") { job1 = job1.with(details: TestDetails(completeTime: 1)) @@ -836,7 +742,7 @@ class JobRunnerSpec: QuickSpec { expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse()) } - // MARK: ------ does not start the job queues if there are no app active jobs and blocking jobs are running + // MARK: -------- does not start the job queues if there are no app active jobs and blocking jobs are running it("does not start the job queues if there are no app active jobs and blocking jobs are running") { job1 = job1.with(details: TestDetails(completeTime: 2)) job2 = Job( @@ -881,7 +787,7 @@ class JobRunnerSpec: QuickSpec { expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse()) } - // MARK: ------ does not start the job queues if there are app active jobs and blocking jobs are running + // MARK: -------- does not start the job queues if there are app active jobs and blocking jobs are running it("does not start the job queues if there are app active jobs and blocking jobs are running") { job1 = Job( id: 100, @@ -939,7 +845,7 @@ class JobRunnerSpec: QuickSpec { expect(jobRunner.isCurrentlyRunning(job1)).to(beFalse()) } - // MARK: ------ starts the job queues if there are no app active jobs + // MARK: -------- starts the job queues if there are no app active jobs it("starts the job queues if there are no app active jobs") { job1 = job1.with(details: TestDetails(completeTime: 1)) jobRunner.appDidFinishLaunching(using: dependencies) @@ -962,7 +868,7 @@ class JobRunnerSpec: QuickSpec { expect(jobRunner.isCurrentlyRunning(job1)).to(beTrue()) } - // MARK: ------ starts the job queues if there are app active jobs + // MARK: -------- starts the job queues if there are app active jobs it("starts the job queues if there are app active jobs") { job1 = Job( id: 100, @@ -1020,7 +926,7 @@ class JobRunnerSpec: QuickSpec { expect(jobRunner.isCurrentlyRunning(job2)).to(beTrue()) } - // MARK: ------ starts the job queues after completing blocking app launch jobs + // MARK: -------- starts the job queues after completing blocking app launch jobs it("starts the job queues after completing blocking app launch jobs") { job1 = job1.with(details: TestDetails(completeTime: 2)) job2 = Job( @@ -1074,7 +980,7 @@ class JobRunnerSpec: QuickSpec { expect(jobRunner.isCurrentlyRunning(job1)).to(beTrue()) } - // MARK: ------ starts the job queues alongside non blocking app launch jobs + // MARK: -------- starts the job queues alongside non blocking app launch jobs it("starts the job queues alongside non blocking app launch jobs") { job1 = job1.with(details: TestDetails(completeTime: 1)) job2 = Job( @@ -1120,9 +1026,9 @@ class JobRunnerSpec: QuickSpec { } } - // MARK: ---- by checking if a job can be added to the queue + // MARK: ------ by checking if a job can be added to the queue context("by checking if a job can be added to the queue") { - // MARK: ------ does not add a general job to the queue before launch + // MARK: -------- does not add a general job to the queue before launch it("does not add a general job to the queue before launch") { job1 = Job( id: 100, @@ -1151,7 +1057,7 @@ class JobRunnerSpec: QuickSpec { expect(jobRunner.allJobInfo()).to(beEmpty()) } - // MARK: ------ adds a launch job to the queue in a pending state before launch + // MARK: -------- adds a launch job to the queue in a pending state before launch it("adds a launch job to the queue in a pending state before launch") { job1 = Job( id: 100, @@ -1180,7 +1086,7 @@ class JobRunnerSpec: QuickSpec { expect(Array(jobRunner.jobInfoFor(state: [.pending]).keys)).to(equal([100])) } - // MARK: ------ does not add a general job to the queue after launch but before becoming active + // MARK: -------- does not add a general job to the queue after launch but before becoming active it("does not add a general job to the queue after launch but before becoming active") { job1 = Job( id: 100, @@ -1210,7 +1116,7 @@ class JobRunnerSpec: QuickSpec { expect(jobRunner.allJobInfo()).to(beEmpty()) } - // MARK: ------ adds a launch job to the queue in a pending state after launch but before becoming active + // MARK: -------- adds a launch job to the queue in a pending state after launch but before becoming active it("adds a launch job to the queue in a pending state after launch but before becoming active") { job1 = Job( id: 100, @@ -1240,7 +1146,7 @@ class JobRunnerSpec: QuickSpec { expect(Array(jobRunner.jobInfoFor(state: .pending).keys)).to(equal([100])) } - // MARK: ------ adds a general job to the queue after becoming active + // MARK: -------- adds a general job to the queue after becoming active it("adds a general job to the queue after becoming active") { job1 = Job( id: 100, @@ -1271,7 +1177,7 @@ class JobRunnerSpec: QuickSpec { expect(Array(jobRunner.allJobInfo().keys)).to(equal([100])) } - // MARK: ------ adds a launch job to the queue and starts it after becoming active + // MARK: -------- adds a launch job to the queue and starts it after becoming active it("adds a launch job to the queue and starts it after becoming active") { job1 = Job( id: 100, @@ -1304,16 +1210,16 @@ class JobRunnerSpec: QuickSpec { } } - // MARK: -- when running jobs + // MARK: ---- when running jobs context("when running jobs") { beforeEach { jobRunner.appDidFinishLaunching(using: dependencies) jobRunner.appDidBecomeActive(using: dependencies) } - // MARK: ---- by adding + // MARK: ------ by adding context("by adding") { - // MARK: ------ does not start until after the db transaction completes + // MARK: -------- does not start until after the db transaction completes it("does not start until after the db transaction completes") { job1 = job1.with(details: TestDetails(completeTime: 1)) @@ -1327,9 +1233,9 @@ class JobRunnerSpec: QuickSpec { } } - // MARK: ---- with job dependencies + // MARK: ------ with job dependencies context("with job dependencies") { - // MARK: ------ starts dependencies first + // MARK: -------- starts dependencies first it("starts dependencies first") { job1 = job1.with(details: TestDetails(completeTime: 1)) job2 = job2.with(details: TestDetails(completeTime: 2)) @@ -1347,7 +1253,7 @@ class JobRunnerSpec: QuickSpec { .to(equal([101])) } - // MARK: ------ removes the initial job from the queue + // MARK: -------- removes the initial job from the queue it("removes the initial job from the queue") { job1 = job1.with(details: TestDetails(completeTime: 1)) job2 = job2.with(details: TestDetails(completeTime: 2)) @@ -1366,7 +1272,7 @@ class JobRunnerSpec: QuickSpec { expect(jobRunner.jobInfoFor(state: .running, variant: .messageSend).keys).toNot(contain(100)) } - // MARK: ------ starts the initial job when the dependencies succeed + // MARK: -------- starts the initial job when the dependencies succeed it("starts the initial job when the dependencies succeed") { job1 = job1.with(details: TestDetails(completeTime: 2)) job2 = job2.with(details: TestDetails(completeTime: 1)) @@ -1390,7 +1296,7 @@ class JobRunnerSpec: QuickSpec { .to(equal([100])) } - // MARK: ------ does not start the initial job if the dependencies are deferred + // MARK: -------- does not start the initial job if the dependencies are deferred it("does not start the initial job if the dependencies are deferred") { job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2)) job2 = job2.with(details: TestDetails(result: .deferred, completeTime: 1)) @@ -1413,7 +1319,7 @@ class JobRunnerSpec: QuickSpec { expect(Array(jobRunner.jobInfoFor(state: .running).keys)).to(beEmpty()) } - // MARK: ------ does not start the initial job if the dependencies fail + // MARK: -------- does not start the initial job if the dependencies fail it("does not start the initial job if the dependencies fail") { job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2)) job2 = job2.with(details: TestDetails(result: .failure, completeTime: 1)) @@ -1436,7 +1342,7 @@ class JobRunnerSpec: QuickSpec { expect(Array(jobRunner.jobInfoFor(state: .running).keys)).to(beEmpty()) } - // MARK: ------ does not delete the initial job if the dependencies fail + // MARK: -------- does not delete the initial job if the dependencies fail it("does not delete the initial job if the dependencies fail") { job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2)) job2 = job2.with(details: TestDetails(result: .failure, completeTime: 1)) @@ -1466,7 +1372,7 @@ class JobRunnerSpec: QuickSpec { expect(mockStorage.read { db in try Job.fetchCount(db) }).to(equal(2)) } - // MARK: ------ deletes the initial job if the dependencies permanently fail + // MARK: -------- deletes the initial job if the dependencies permanently fail it("deletes the initial job if the dependencies permanently fail") { job1 = job1.with(details: TestDetails(result: .failure, completeTime: 2)) job2 = job2.with(details: TestDetails(result: .permanentFailure, completeTime: 1)) @@ -1495,16 +1401,16 @@ class JobRunnerSpec: QuickSpec { } } - // MARK: -- when completing jobs + // MARK: ---- when completing jobs context("when completing jobs") { beforeEach { jobRunner.appDidFinishLaunching(using: dependencies) jobRunner.appDidBecomeActive(using: dependencies) } - // MARK: ---- by succeeding + // MARK: ------ by succeeding context("by succeeding") { - // MARK: ------ removes the job from the queue + // MARK: -------- removes the job from the queue it("removes the job from the queue") { job1 = job1.with(details: TestDetails(result: .success, completeTime: 1)) @@ -1522,7 +1428,7 @@ class JobRunnerSpec: QuickSpec { expect(Array(jobRunner.jobInfoFor(state: .running).keys)).to(beEmpty()) } - // MARK: ------ deletes the job + // MARK: -------- deletes the job it("deletes the job") { job1 = job1.with(details: TestDetails(result: .success, completeTime: 1)) @@ -1544,9 +1450,9 @@ class JobRunnerSpec: QuickSpec { } } - // MARK: ---- by deferring + // MARK: ------ by deferring context("by deferring") { - // MARK: ------ reschedules the job to run again later + // MARK: -------- reschedules the job to run again later it("reschedules the job to run again later") { job1 = job1.with(details: TestDetails(result: .deferred, completeTime: 1)) @@ -1571,7 +1477,7 @@ class JobRunnerSpec: QuickSpec { )) } - // MARK: ------ does not delete the job + // MARK: -------- does not delete the job it("does not delete the job") { job1 = job1.with(details: TestDetails(result: .deferred, completeTime: 1)) @@ -1592,7 +1498,7 @@ class JobRunnerSpec: QuickSpec { expect(mockStorage.read { db in try Job.fetchCount(db) }).toNot(equal(0)) } - // MARK: ------ fails the job if it is deferred too many times + // MARK: -------- fails the job if it is deferred too many times it("fails the job if it is deferred too many times") { job1 = job1.with(details: TestDetails(result: .deferred, completeTime: 1)) @@ -1666,9 +1572,9 @@ class JobRunnerSpec: QuickSpec { } } - // MARK: ---- by failing + // MARK: ------ by failing context("by failing") { - // MARK: ------ removes the job from the queue + // MARK: -------- removes the job from the queue it("removes the job from the queue") { job1 = job1.with(details: TestDetails(result: .failure, completeTime: 1)) @@ -1686,7 +1592,7 @@ class JobRunnerSpec: QuickSpec { expect(Array(jobRunner.jobInfoFor(state: .running).keys)).to(beEmpty()) } - // MARK: ------ does not delete the job + // MARK: -------- does not delete the job it("does not delete the job") { job1 = job1.with(details: TestDetails(result: .failure, completeTime: 1)) @@ -1708,9 +1614,9 @@ class JobRunnerSpec: QuickSpec { } } - // MARK: ---- by permanently failing + // MARK: ------ by permanently failing context("by permanently failing") { - // MARK: ------ removes the job from the queue + // MARK: -------- removes the job from the queue it("removes the job from the queue") { job1 = job1.with(details: TestDetails(result: .permanentFailure, completeTime: 1)) @@ -1728,7 +1634,7 @@ class JobRunnerSpec: QuickSpec { expect(Array(jobRunner.jobInfoFor(state: .running).keys)).to(beEmpty()) } - // MARK: ------ deletes the job + // MARK: -------- deletes the job it("deletes the job") { job1 = job1.with(details: TestDetails(result: .permanentFailure, completeTime: 1)) @@ -1753,3 +1659,90 @@ class JobRunnerSpec: QuickSpec { } } } + +// MARK: - Test Types + +fileprivate struct TestDetails: Codable { + enum ResultType: Codable { + case success + case failure + case permanentFailure + case deferred + } + + public let result: ResultType + public let completeTime: Int + public let intValue: Int64 + public let stringValue: String + + init( + result: ResultType = .success, + completeTime: Int = 0, + intValue: Int64 = 100, + stringValue: String = "200" + ) { + self.result = result + self.completeTime = completeTime + self.intValue = intValue + self.stringValue = stringValue + } +} + +fileprivate struct InvalidDetails: Codable { + func encode(to encoder: Encoder) throws { throw HTTPError.parsingFailed } +} + +fileprivate enum TestJob: JobExecutor { + static let maxFailureCount: Int = 1 + static let requiresThreadId: Bool = false + static let requiresInteractionId: Bool = false + + static func run( + _ job: Job, + queue: DispatchQueue, + success: @escaping (Job, Bool, Dependencies) -> (), + failure: @escaping (Job, Error?, Bool, Dependencies) -> (), + deferred: @escaping (Job, Dependencies) -> (), + using dependencies: Dependencies + ) { + guard + let detailsData: Data = job.details, + let details: TestDetails = try? JSONDecoder().decode(TestDetails.self, from: detailsData) + else { return success(job, true, dependencies) } + + let completeJob: () -> () = { + // Need to increase the 'completeTime' and 'nextRunTimestamp' to prevent the job + // from immediately being run again or immediately completing afterwards + let updatedJob: Job = job + .with(nextRunTimestamp: TimeInterval(details.completeTime + 1)) + .with( + details: TestDetails( + result: details.result, + completeTime: (details.completeTime + 2), + intValue: details.intValue, + stringValue: details.stringValue + ) + )! + dependencies.storage.write { db in try _ = updatedJob.saved(db) } + + switch details.result { + case .success: success(job, true, dependencies) + case .failure: failure(job, nil, false, dependencies) + case .permanentFailure: failure(job, nil, true, dependencies) + case .deferred: deferred(updatedJob, dependencies) + } + } + + guard dependencies.fixedTime < details.completeTime else { + return queue.async(using: dependencies) { + completeJob() + } + } + + dependencies.asyncExecutions.appendTo(details.completeTime) { + queue.async(using: dependencies) { + completeJob() + } + } + } +} diff --git a/SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift b/SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift index d3349a15a..5356d7fb4 100644 --- a/SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift +++ b/SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift @@ -9,21 +9,40 @@ import Nimble @testable import SessionUtilitiesKit class BatchResponseSpec: QuickSpec { - struct TestType: Codable, Equatable { - let stringValue: String - } - struct TestType2: Codable, Equatable { - let intValue: Int - let stringValue2: String - } - - // MARK: - Spec - - override func spec() { - // MARK: - HTTP.BatchSubResponse + override class func spec() { + // MARK: Configuration + @TestState var responseInfo: ResponseInfoType! = HTTP.ResponseInfo(code: 200, headers: [:]) + @TestState var testType: TestType! = TestType(stringValue: "test1") + @TestState var testType2: TestType2! = TestType2(intValue: 123, stringValue2: "test2") + @TestState var data: Data! = """ + [\([ + try! JSONEncoder().with(outputFormatting: .sortedKeys).encode( + HTTP.BatchSubResponse( + code: 200, + headers: [:], + body: testType, + failedToParseBody: false + ) + ), + try! JSONEncoder().with(outputFormatting: .sortedKeys).encode( + HTTP.BatchSubResponse( + code: 200, + headers: [:], + body: testType2, + failedToParseBody: false + ) + ) + ] + .map { String(data: $0, encoding: .utf8)! } + .joined(separator: ","))] + """.data(using: .utf8)! + + // MARK: - an HTTP.BatchSubResponse describe("an HTTP.BatchSubResponse") { + // MARK: -- when decoding context("when decoding") { + // MARK: ---- decodes correctly it("decodes correctly") { let jsonString: String = """ { @@ -45,6 +64,7 @@ class BatchResponseSpec: QuickSpec { expect(subResponse?.body).toNot(beNil()) } + // MARK: ---- decodes with invalid body data it("decodes with invalid body data") { let jsonString: String = """ { @@ -63,6 +83,7 @@ class BatchResponseSpec: QuickSpec { expect(subResponse).toNot(beNil()) } + // MARK: ---- flags invalid body data as invalid it("flags invalid body data as invalid") { let jsonString: String = """ { @@ -83,6 +104,7 @@ class BatchResponseSpec: QuickSpec { expect(subResponse?.failedToParseBody).to(beTrue()) } + // MARK: ---- does not flag a missing or invalid optional body as invalid it("does not flag a missing or invalid optional body as invalid") { let jsonString: String = """ { @@ -102,6 +124,7 @@ class BatchResponseSpec: QuickSpec { expect(subResponse?.failedToParseBody).to(beFalse()) } + // MARK: ---- does not flag a NoResponse body as invalid it("does not flag a NoResponse body as invalid") { let jsonString: String = """ { @@ -123,10 +146,9 @@ class BatchResponseSpec: QuickSpec { } } - // MARK: - Convenience - // MARK: --Decodable - + // MARK: - a Decodable describe("a Decodable") { + // MARK: -- decodes correctly it("decodes correctly") { let jsonData: Data = "{\"stringValue\":\"testValue\"}".data(using: .utf8)! let result: TestType? = try? TestType.decoded(from: jsonData) @@ -135,42 +157,9 @@ class BatchResponseSpec: QuickSpec { } } - // MARK: - --Combine - + // MARK: - a (ResponseInfoType, Data?) Publisher describe("a (ResponseInfoType, Data?) Publisher") { - var responseInfo: ResponseInfoType! - var testType: TestType! - var testType2: TestType2! - var data: Data! - - beforeEach { - responseInfo = HTTP.ResponseInfo(code: 200, headers: [:]) - testType = TestType(stringValue: "test1") - testType2 = TestType2(intValue: 123, stringValue2: "test2") - data = """ - [\([ - try! JSONEncoder().with(outputFormatting: .sortedKeys).encode( - HTTP.BatchSubResponse( - code: 200, - headers: [:], - body: testType, - failedToParseBody: false - ) - ), - try! JSONEncoder().with(outputFormatting: .sortedKeys).encode( - HTTP.BatchSubResponse( - code: 200, - headers: [:], - body: testType2, - failedToParseBody: false - ) - ) - ] - .map { String(data: $0, encoding: .utf8)! } - .joined(separator: ","))] - """.data(using: .utf8)! - } - + // MARK: -- decodes valid data correctly it("decodes valid data correctly") { var result: HTTP.BatchResponse? Just((responseInfo, data)) @@ -191,6 +180,7 @@ class BatchResponseSpec: QuickSpec { .to(equal(testType2)) } + // MARK: -- fails if there is no data it("fails if there is no data") { var error: Error? Just((responseInfo, nil)) @@ -203,6 +193,7 @@ class BatchResponseSpec: QuickSpec { expect(error).to(matchError(HTTPError.parsingFailed)) } + // MARK: -- fails if the data is not JSON it("fails if the data is not JSON") { var error: Error? Just((responseInfo, Data([1, 2, 3]))) @@ -215,6 +206,7 @@ class BatchResponseSpec: QuickSpec { expect(error).to(matchError(HTTPError.parsingFailed)) } + // MARK: -- fails if the data is not a JSON array it("fails if the data is not a JSON array") { var error: Error? Just((responseInfo, "{}".data(using: .utf8))) @@ -227,6 +219,7 @@ class BatchResponseSpec: QuickSpec { expect(error).to(matchError(HTTPError.parsingFailed)) } + // MARK: -- fails if the JSON array does not have the same number of items as the expected types it("fails if the JSON array does not have the same number of items as the expected types") { var error: Error? Just((responseInfo, data)) @@ -243,6 +236,7 @@ class BatchResponseSpec: QuickSpec { expect(error).to(matchError(HTTPError.parsingFailed)) } + // MARK: -- fails if one of the JSON array values fails to decode it("fails if one of the JSON array values fails to decode") { data = """ [\([ @@ -275,3 +269,13 @@ class BatchResponseSpec: QuickSpec { } } } + +// MARK: - Test Types + +fileprivate struct TestType: Codable, Equatable { + let stringValue: String +} +fileprivate struct TestType2: Codable, Equatable { + let intValue: Int + let stringValue2: String +} diff --git a/SessionUtilitiesKitTests/Utilities/BencodeSpec.swift b/SessionUtilitiesKitTests/Utilities/BencodeSpec.swift index 3ddbdc2a3..0ed525738 100644 --- a/SessionUtilitiesKitTests/Utilities/BencodeSpec.swift +++ b/SessionUtilitiesKitTests/Utilities/BencodeSpec.swift @@ -8,42 +8,12 @@ import Nimble @testable import SessionUtilitiesKit class BencodeSpec: QuickSpec { - struct TestType: Codable, Equatable { - let intValue: Int - let stringValue: String - } - - struct TestType2: Codable, Equatable { - let stringValue: String - let boolValue: Bool - } - - struct TestType3: Codable, Equatable { - let stringValue: String - let boolValue: Bool - - init(_ stringValue: String, _ boolValue: Bool) { - self.stringValue = stringValue - self.boolValue = boolValue - } - - init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - - self = TestType3( - try container.decode(String.self, forKey: .stringValue), - ((try? container.decode(Bool.self, forKey: .boolValue)) ?? false) - ) - } - } - - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - Bencode describe("Bencode") { - // MARK: - when decoding + // MARK: -- when decoding context("when decoding") { - // MARK: -- should decode a basic string + // MARK: ---- should decode a basic string it("should decode a basic string") { let basicStringData: Data = "5:howdy".data(using: .utf8)! let result = try? Bencode.decode(String.self, from: basicStringData) @@ -51,7 +21,7 @@ class BencodeSpec: QuickSpec { expect(result).to(equal("howdy")) } - // MARK: -- should decode a basic integer + // MARK: ---- should decode a basic integer it("should decode a basic integer") { let basicIntegerData: Data = "i3e".data(using: .utf8)! let result = try? Bencode.decode(Int.self, from: basicIntegerData) @@ -59,7 +29,7 @@ class BencodeSpec: QuickSpec { expect(result).to(equal(3)) } - // MARK: -- should decode a list of integers + // MARK: ---- should decode a list of integers it("should decode a list of integers") { let basicIntListData: Data = "li1ei2ee".data(using: .utf8)! let result = try? Bencode.decode([Int].self, from: basicIntListData) @@ -67,7 +37,7 @@ class BencodeSpec: QuickSpec { expect(result).to(equal([1, 2])) } - // MARK: -- should decode a basic dict + // MARK: ---- should decode a basic dict it("should decode a basic dict") { let basicDictData: Data = "d4:spaml1:a1:bee".data(using: .utf8)! let result = try? Bencode.decode([String: [String]].self, from: basicDictData) @@ -75,7 +45,7 @@ class BencodeSpec: QuickSpec { expect(result).to(equal(["spam": ["a", "b"]])) } - // MARK: -- decodes a decodable type + // MARK: ---- decodes a decodable type it("decodes a decodable type") { let data: Data = "d8:intValuei100e11:stringValue4:Test".data(using: .utf8)! let result: TestType? = try? Bencode.decode(TestType.self, from: data) @@ -83,7 +53,7 @@ class BencodeSpec: QuickSpec { expect(result).to(equal(TestType(intValue: 100, stringValue: "Test"))) } - // MARK: -- decodes a stringified decodable type + // MARK: ---- decodes a stringified decodable type it("decodes a stringified decodable type") { let data: Data = "37:{\"intValue\":100,\"stringValue\":\"Test\"}".data(using: .utf8)! let result: TestType? = try? Bencode.decode(TestType.self, from: data) @@ -92,11 +62,11 @@ class BencodeSpec: QuickSpec { } } - // MARK: - when decoding a response + // MARK: -- when decoding a response context("when decoding a response") { - // MARK: -- with a decodable type + // MARK: ---- with a decodable type context("with a decodable type") { - // MARK: ---- decodes successfully + // MARK: ------ decodes successfully it("decodes successfully") { let data: Data = "ld8:intValuei100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e" .data(using: .utf8)! @@ -114,7 +84,7 @@ class BencodeSpec: QuickSpec { )) } - // MARK: -- decodes successfully with no body + // MARK: ------ decodes successfully with no body it("decodes successfully with no body") { let data: Data = "ld8:intValuei100e11:stringValue4:Teste" .data(using: .utf8)! @@ -132,7 +102,7 @@ class BencodeSpec: QuickSpec { )) } - // MARK: ---- throws a parsing error when given an invalid length + // MARK: ------ throws a parsing error when given an invalid length it("throws a parsing error when given an invalid length") { let data: Data = "ld12:intValuei100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e" .data(using: .utf8)! @@ -143,7 +113,7 @@ class BencodeSpec: QuickSpec { }.to(throwError(HTTPError.parsingFailed)) } - // MARK: ---- throws a parsing error when given an invalid key + // MARK: ------ throws a parsing error when given an invalid key it("throws a parsing error when given an invalid key") { let data: Data = "ld7:INVALIDi100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e" .data(using: .utf8)! @@ -154,7 +124,7 @@ class BencodeSpec: QuickSpec { }.to(throwError(HTTPError.parsingFailed)) } - // MARK: ---- decodes correctly when trying to decode an int to a bool with custom handling + // MARK: ------ decodes correctly when trying to decode an int to a bool with custom handling it("decodes correctly when trying to decode an int to a bool with custom handling") { let data: Data = "ld9:boolValuei1e11:stringValue4:testee" .data(using: .utf8)! @@ -165,7 +135,7 @@ class BencodeSpec: QuickSpec { }.toNot(throwError(HTTPError.parsingFailed)) } - // MARK: ---- throws a parsing error when trying to decode an int to a bool + // MARK: ------ throws a parsing error when trying to decode an int to a bool it("throws a parsing error when trying to decode an int to a bool") { let data: Data = "ld9:boolValuei1e11:stringValue4:testee" .data(using: .utf8)! @@ -177,9 +147,9 @@ class BencodeSpec: QuickSpec { } } - // MARK: -- with stringified json info + // MARK: ---- with stringified json info context("with stringified json info") { - // MARK: -- decodes successfully + // MARK: ------ decodes successfully it("decodes successfully") { let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e" .data(using: .utf8)! @@ -197,7 +167,7 @@ class BencodeSpec: QuickSpec { )) } - // MARK: -- decodes successfully with no body + // MARK: ------ decodes successfully with no body it("decodes successfully with no body") { let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}e" .data(using: .utf8)! @@ -215,7 +185,7 @@ class BencodeSpec: QuickSpec { )) } - // MARK: -- throws a parsing error when invalid + // MARK: ------ throws a parsing error when invalid it("throws a parsing error when invalid") { let data: Data = "l36:{\"INVALID\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e" .data(using: .utf8)! @@ -227,9 +197,9 @@ class BencodeSpec: QuickSpec { } } - // MARK: -- with a string value + // MARK: ---- with a string value context("with a string value") { - // MARK: ---- decodes successfully + // MARK: ------ decodes successfully it("decodes successfully") { let data: Data = "l4:Test5:\u{01}\u{02}\u{03}\u{04}\u{05}e".data(using: .utf8)! let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) @@ -243,7 +213,7 @@ class BencodeSpec: QuickSpec { )) } - // MARK: ---- decodes successfully with no body + // MARK: ------ decodes successfully with no body it("decodes successfully with no body") { let data: Data = "l4:Teste".data(using: .utf8)! let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) @@ -257,7 +227,7 @@ class BencodeSpec: QuickSpec { )) } - // MARK: ---- throws a parsing error when invalid + // MARK: ------ throws a parsing error when invalid it("throws a parsing error when invalid") { let data: Data = "l10:Teste".data(using: .utf8)! @@ -268,9 +238,9 @@ class BencodeSpec: QuickSpec { } } - // MARK: -- with an int value + // MARK: ---- with an int value context("with an int value") { - // MARK: ---- decodes successfully + // MARK: ------ decodes successfully it("decodes successfully") { let data: Data = "li100e5:\u{01}\u{02}\u{03}\u{04}\u{05}e".data(using: .utf8)! let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) @@ -284,7 +254,7 @@ class BencodeSpec: QuickSpec { )) } - // MARK: ---- decodes successfully with no body + // MARK: ------ decodes successfully with no body it("decodes successfully with no body") { let data: Data = "li100ee".data(using: .utf8)! let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) @@ -298,7 +268,7 @@ class BencodeSpec: QuickSpec { )) } - // MARK: ---- throws a parsing error when invalid + // MARK: ------ throws a parsing error when invalid it("throws a parsing error when invalid") { let data: Data = "l4:Teste".data(using: .utf8)! @@ -312,3 +282,34 @@ class BencodeSpec: QuickSpec { } } } + +// MARK: - Test Types + +fileprivate struct TestType: Codable, Equatable { + let intValue: Int + let stringValue: String +} + +fileprivate struct TestType2: Codable, Equatable { + let stringValue: String + let boolValue: Bool +} + +fileprivate struct TestType3: Codable, Equatable { + let stringValue: String + let boolValue: Bool + + init(_ stringValue: String, _ boolValue: Bool) { + self.stringValue = stringValue + self.boolValue = boolValue + } + + init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + self = TestType3( + try container.decode(String.self, forKey: .stringValue), + ((try? container.decode(Bool.self, forKey: .boolValue)) ?? false) + ) + } +} diff --git a/SessionUtilitiesKitTests/Utilities/VersionSpec.swift b/SessionUtilitiesKitTests/Utilities/VersionSpec.swift index 55c25ba4d..b3d05ad69 100644 --- a/SessionUtilitiesKitTests/Utilities/VersionSpec.swift +++ b/SessionUtilitiesKitTests/Utilities/VersionSpec.swift @@ -8,10 +8,10 @@ import Nimble @testable import SessionUtilitiesKit class VersionSpec: QuickSpec { - // MARK: - Spec - - override func spec() { + override class func spec() { + // MARK: - a Version describe("a Version") { + // MARK: -- can be created from a string it("can be created from a string") { let version: Version = Version.from("1.20.3") @@ -20,13 +20,16 @@ class VersionSpec: QuickSpec { expect(version.patch).to(equal(3)) } + // MARK: -- correctly exposes a string value it("correctly exposes a string value") { let version: Version = Version(major: 1, minor: 20, patch: 3) expect(version.stringValue).to(equal("1.20.3")) } + // MARK: -- when checking equality context("when checking equality") { + // MARK: ---- returns true if the values match it("returns true if the values match") { let version1: Version = Version.from("1.0.0") let version2: Version = Version.from("1.0.0") @@ -35,6 +38,7 @@ class VersionSpec: QuickSpec { .to(beTrue()) } + // MARK: ---- returns false if the values do not match it("returns false if the values do not match") { let version1: Version = Version.from("1.0.0") let version2: Version = Version.from("1.0.1") @@ -44,7 +48,9 @@ class VersionSpec: QuickSpec { } } + // MARK: -- when comparing versions context("when comparing versions") { + // MARK: ---- returns correctly for a simple major difference it("returns correctly for a simple major difference") { let version1: Version = Version.from("1.0.0") let version2: Version = Version.from("2.0.0") @@ -53,6 +59,7 @@ class VersionSpec: QuickSpec { expect(version2 > version1).to(beTrue()) } + // MARK: ---- returns correctly for a complex major difference it("returns correctly for a complex major difference") { let version1a: Version = Version.from("2.90.90") let version2a: Version = Version.from("10.0.0") @@ -65,6 +72,7 @@ class VersionSpec: QuickSpec { expect(version2b > version1b).to(beTrue()) } + // MARK: ---- returns correctly for a simple minor difference it("returns correctly for a simple minor difference") { let version1: Version = Version.from("1.0.0") let version2: Version = Version.from("1.1.0") @@ -73,6 +81,7 @@ class VersionSpec: QuickSpec { expect(version2 > version1).to(beTrue()) } + // MARK: ---- returns correctly for a complex minor difference it("returns correctly for a complex minor difference") { let version1a: Version = Version.from("90.2.90") let version2a: Version = Version.from("90.10.0") @@ -85,6 +94,7 @@ class VersionSpec: QuickSpec { expect(version2b > version1b).to(beTrue()) } + // MARK: ---- returns correctly for a simple patch difference it("returns correctly for a simple patch difference") { let version1: Version = Version.from("1.0.0") let version2: Version = Version.from("1.0.1") @@ -93,6 +103,7 @@ class VersionSpec: QuickSpec { expect(version2 > version1).to(beTrue()) } + // MARK: ---- returns correctly for a complex patch difference it("returns correctly for a complex patch difference") { let version1a: Version = Version.from("90.90.2") let version2a: Version = Version.from("90.90.10") diff --git a/_SharedTestUtilities/Mock.swift b/_SharedTestUtilities/Mock.swift index aed05ca40..042de35ad 100644 --- a/_SharedTestUtilities/Mock.swift +++ b/_SharedTestUtilities/Mock.swift @@ -17,9 +17,13 @@ public class Mock { // MARK: - Initialization - internal required init(functionHandler: MockFunctionHandler? = nil) { + internal required init( + functionHandler: MockFunctionHandler? = nil, + initialSetup: ((Mock) -> ())? = nil + ) { self.functionConsumer = FunctionConsumer() self.functionHandler = (functionHandler ?? self.functionConsumer) + initialSetup?(self) } // MARK: - MockFunctionHandler @@ -103,7 +107,7 @@ internal class MockFunction { internal class MockFunctionBuilder: MockFunctionHandler { private let callBlock: (inout T) throws -> R - private let mockInit: (MockFunctionHandler?) -> Mock + private let mockInit: (MockFunctionHandler?, ((Mock) -> ())?) -> Mock private var functionName: String? private var parameterCount: Int? private var parameterSummary: String? @@ -113,7 +117,7 @@ internal class MockFunctionBuilder: MockFunctionHandler { // MARK: - Initialization - init(_ callBlock: @escaping (inout T) throws -> R, mockInit: @escaping (MockFunctionHandler?) -> Mock) { + init(_ callBlock: @escaping (inout T) throws -> R, mockInit: @escaping (MockFunctionHandler?, ((Mock) -> ())?) -> Mock) { self.callBlock = callBlock self.mockInit = mockInit } @@ -141,7 +145,7 @@ internal class MockFunctionBuilder: MockFunctionHandler { // MARK: - Build func build() throws -> MockFunction { - var completionMock = mockInit(self) as! T + var completionMock = mockInit(self, nil) as! T _ = try? callBlock(&completionMock) guard let name: String = functionName, let parameterCount: Int = parameterCount, let parameterSummary: String = parameterSummary else { diff --git a/_SharedTestUtilities/MockCaches.swift b/_SharedTestUtilities/MockCaches.swift index 493f2c579..94125118a 100644 --- a/_SharedTestUtilities/MockCaches.swift +++ b/_SharedTestUtilities/MockCaches.swift @@ -17,6 +17,11 @@ class MockCaches: CachesType { set { cacheInstances[cache.key] = newValue.map { cache.mutableInstance($0) } } } + public func setting(cache: CacheInfo.Config, to value: M?) -> MockCaches { + self[cache] = value + return self + } + // MARK: - Mutable Access @discardableResult public func mutate( diff --git a/SessionMessagingKitTests/_TestUtilities/MockUserDefaults.swift b/_SharedTestUtilities/MockUserDefaults.swift similarity index 100% rename from SessionMessagingKitTests/_TestUtilities/MockUserDefaults.swift rename to _SharedTestUtilities/MockUserDefaults.swift diff --git a/_SharedTestUtilities/Mocked.swift b/_SharedTestUtilities/Mocked.swift index e5e9e9cd2..323c47cc9 100644 --- a/_SharedTestUtilities/Mocked.swift +++ b/_SharedTestUtilities/Mocked.swift @@ -1,6 +1,7 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation +import GRDB import SessionUtilitiesKit // MARK: - Mocked @@ -32,6 +33,20 @@ func any() -> Double { 0 } func any() -> String { "" } func any() -> Data { Data() } func any() -> Bool { false } +func any() -> Dependencies { + Dependencies( + storage: SynchronousStorage(customWriter: try! DatabaseQueue()), + network: MockNetwork(), + crypto: MockCrypto(), + standardUserDefaults: MockUserDefaults(), + caches: MockCaches(), + jobRunner: MockJobRunner(), + scheduler: .immediate, + dateNow: Date(timeIntervalSince1970: 1234567890), + fixedTime: 0, + forceSynchronous: true + ) +} func anyAny() -> Any { 0 } // Unique name for compilation performance reasons func anyArray() -> [R] { [] } // Unique name for compilation performance reasons diff --git a/_SharedTestUtilities/NimbleExtensions.swift b/_SharedTestUtilities/NimbleExtensions.swift index 12b5d740d..cfb64752b 100644 --- a/_SharedTestUtilities/NimbleExtensions.swift +++ b/_SharedTestUtilities/NimbleExtensions.swift @@ -29,7 +29,7 @@ public func call( matchingParameters: Bool = false, exclusive: Bool = false, functionBlock: @escaping (inout T) throws -> R -) -> Predicate where M: Mock { +) -> Nimble.Predicate where M: Mock { return Predicate.define { actualExpression in let callInfo: CallInfo = generateCallInfo(actualExpression, functionBlock) let matchingParameterRecords: [String] = callInfo.desiredFunctionCalls diff --git a/_SharedTestUtilities/SynchronousStorage.swift b/_SharedTestUtilities/SynchronousStorage.swift index 86471f988..66680cd2e 100644 --- a/_SharedTestUtilities/SynchronousStorage.swift +++ b/_SharedTestUtilities/SynchronousStorage.swift @@ -6,6 +6,16 @@ import GRDB @testable import SessionUtilitiesKit class SynchronousStorage: Storage { + public init( + customWriter: DatabaseWriter? = nil, + customMigrationTargets: [MigratableTarget.Type]? = nil, + initialData: ((Database) throws -> ())? = nil + ) { + super.init(customWriter: customWriter, customMigrationTargets: customMigrationTargets) + + write { db in try initialData?(db) } + } + @discardableResult override func write( fileName: String = #file, functionName: String = #function, From b280c0a852b6c8d03e822f9f60ef2507be8dc667 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 22 Sep 2023 15:00:49 +1000 Subject: [PATCH 11/12] Increased Build and Version Numbers and removed extra strings --- Session.xcodeproj/project.pbxproj | 24 +++++++++---------- .../Translations/en.lproj/Localizable.strings | 2 -- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 9527da2ba..d6b5cc95e 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -6507,7 +6507,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 424; + CURRENT_PROJECT_VERSION = 425; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6531,7 +6531,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.4.1; + MARKETING_VERSION = 2.4.2; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -6579,7 +6579,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 424; + CURRENT_PROJECT_VERSION = 425; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6608,7 +6608,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.4.1; + MARKETING_VERSION = 2.4.2; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -6644,7 +6644,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 424; + CURRENT_PROJECT_VERSION = 425; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6667,7 +6667,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.4.1; + MARKETING_VERSION = 2.4.2; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6718,7 +6718,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 424; + CURRENT_PROJECT_VERSION = 425; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6746,7 +6746,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.4.1; + MARKETING_VERSION = 2.4.2; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -7678,7 +7678,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 424; + CURRENT_PROJECT_VERSION = 425; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7716,7 +7716,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 2.4.1; + MARKETING_VERSION = 2.4.2; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -7749,7 +7749,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 424; + CURRENT_PROJECT_VERSION = 425; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7787,7 +7787,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 2.4.1; + MARKETING_VERSION = 2.4.2; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 3dc2829e3..d91f647bc 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -635,8 +635,6 @@ "message_info_title" = "Message Info"; "mute_button_text" = "Mute"; "unmute_button_text" = "Unmute"; -"mark_read_button_text" = "Mark read"; -"mark_unread_button_text" = "Mark unread"; "leave_group_confirmation_alert_title" = "Leave Group"; "leave_community_confirmation_alert_title" = "Leave Community"; "leave_community_confirmation_alert_message" = "Are you sure you want to leave %@?"; From 1d0733baa755e6d700ba78cae0a0c708f3e12079 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 22 Sep 2023 16:30:55 +1000 Subject: [PATCH 12/12] Added additional new disappearing messages strings --- .../Translations/ar.lproj/Localizable.strings | 27 ++++ .../Translations/be.lproj/Localizable.strings | 27 ++++ .../Translations/bg.lproj/Localizable.strings | 27 ++++ .../Translations/bn.lproj/Localizable.strings | 27 ++++ .../Translations/cs.lproj/Localizable.strings | 27 ++++ .../Translations/da.lproj/Localizable.strings | 27 ++++ .../Translations/de.lproj/Localizable.strings | 27 ++++ .../Translations/el.lproj/Localizable.strings | 27 ++++ .../Translations/en.lproj/Localizable.strings | 146 +++++++++++++++++- .../Translations/eo.lproj/Localizable.strings | 27 ++++ .../es-ES.lproj/Localizable.strings | 27 ++++ .../Translations/fa.lproj/Localizable.strings | 27 ++++ .../Translations/fi.lproj/Localizable.strings | 27 ++++ .../fil.lproj/Localizable.strings | 27 ++++ .../Translations/fr.lproj/Localizable.strings | 27 ++++ .../Translations/hi.lproj/Localizable.strings | 27 ++++ .../Translations/hr.lproj/Localizable.strings | 27 ++++ .../Translations/hu.lproj/Localizable.strings | 27 ++++ .../Translations/id.lproj/Localizable.strings | 27 ++++ .../Translations/it.lproj/Localizable.strings | 27 ++++ .../Translations/ja.lproj/Localizable.strings | 27 ++++ .../Translations/ko.lproj/Localizable.strings | 27 ++++ .../Translations/ku.lproj/Localizable.strings | 27 ++++ .../Translations/lt.lproj/Localizable.strings | 27 ++++ .../Translations/lv.lproj/Localizable.strings | 27 ++++ .../ne-NP.lproj/Localizable.strings | 27 ++++ .../Translations/nl.lproj/Localizable.strings | 27 ++++ .../Translations/no.lproj/Localizable.strings | 27 ++++ .../Translations/pl.lproj/Localizable.strings | 27 ++++ .../pt-BR.lproj/Localizable.strings | 27 ++++ .../pt-PT.lproj/Localizable.strings | 27 ++++ .../Translations/ro.lproj/Localizable.strings | 27 ++++ .../Translations/ru.lproj/Localizable.strings | 27 ++++ .../si-LK.lproj/Localizable.strings | 27 ++++ .../Translations/sk.lproj/Localizable.strings | 27 ++++ .../Translations/sl.lproj/Localizable.strings | 27 ++++ .../sv-SE.lproj/Localizable.strings | 27 ++++ .../Translations/th.lproj/Localizable.strings | 27 ++++ .../Translations/tr.lproj/Localizable.strings | 27 ++++ .../Translations/uk.lproj/Localizable.strings | 27 ++++ .../Translations/vi.lproj/Localizable.strings | 27 ++++ .../zh-CN.lproj/Localizable.strings | 27 ++++ .../zh-TW.lproj/Localizable.strings | 27 ++++ 43 files changed, 1277 insertions(+), 3 deletions(-) diff --git a/Session/Meta/Translations/ar.lproj/Localizable.strings b/Session/Meta/Translations/ar.lproj/Localizable.strings index cb4148379..e323a8301 100644 --- a/Session/Meta/Translations/ar.lproj/Localizable.strings +++ b/Session/Meta/Translations/ar.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/be.lproj/Localizable.strings b/Session/Meta/Translations/be.lproj/Localizable.strings index f377c4606..1a21254b9 100644 --- a/Session/Meta/Translations/be.lproj/Localizable.strings +++ b/Session/Meta/Translations/be.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/bg.lproj/Localizable.strings b/Session/Meta/Translations/bg.lproj/Localizable.strings index 8be9e35ca..2f38f1657 100644 --- a/Session/Meta/Translations/bg.lproj/Localizable.strings +++ b/Session/Meta/Translations/bg.lproj/Localizable.strings @@ -781,3 +781,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/bn.lproj/Localizable.strings b/Session/Meta/Translations/bn.lproj/Localizable.strings index aa72c4917..b85acab03 100644 --- a/Session/Meta/Translations/bn.lproj/Localizable.strings +++ b/Session/Meta/Translations/bn.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/cs.lproj/Localizable.strings b/Session/Meta/Translations/cs.lproj/Localizable.strings index 4a5990d42..4b252fab3 100644 --- a/Session/Meta/Translations/cs.lproj/Localizable.strings +++ b/Session/Meta/Translations/cs.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/da.lproj/Localizable.strings b/Session/Meta/Translations/da.lproj/Localizable.strings index 05035242e..bee1fa5d2 100644 --- a/Session/Meta/Translations/da.lproj/Localizable.strings +++ b/Session/Meta/Translations/da.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 29dcb22b0..b021cdf1e 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/el.lproj/Localizable.strings b/Session/Meta/Translations/el.lproj/Localizable.strings index 2a8a6cd71..c20a33f29 100644 --- a/Session/Meta/Translations/el.lproj/Localizable.strings +++ b/Session/Meta/Translations/el.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index d91f647bc..d755f7010 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -574,7 +574,7 @@ "DISAPPERING_MESSAGES_TIMER_TITLE" = "Timer"; "DISAPPERING_MESSAGES_SAVE_TITLE" = "Set"; "DISAPPERING_MESSAGES_GROUP_WARNING" = "This setting applies to everyone in this conversation."; -"DISAPPERING_MESSAGES_GROUP_WARNING_ADMIN_ONLY" = "This setting applies to everyone in this conversation. Only group admins can change this setting."; +"DISAPPERING_MESSAGES_GROUP_WARNING_ADMIN_ONLY" = "This setting applies to everyone in this conversation.\nOnly group admins can change this setting."; "DISAPPERING_MESSAGES_SUMMARY" = "Disappear After %@ - %@"; "DISAPPERING_MESSAGES_INFO_ENABLE" = "%@ has set messages to disappear %@ after they have been %@"; "DISAPPERING_MESSAGES_INFO_UPDATE" = "%@ has changed messages to disappear %@ after they have been %@"; @@ -610,60 +610,200 @@ /* PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION */ "PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations."; +/* Information displayed above the input when sending a message to a new user for the first time explaining limitations around the types of messages which can be sent before being approved */ "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; + +/* State of a message while it's still in the process of being sent */ "MESSAGE_DELIVERY_STATUS_SENDING" = "Sending"; + +/* State of a message once it has been sent */ "MESSAGE_DELIVERY_STATUS_SENT" = "Sent"; + +/* State of a message after the recipient has read the message */ "MESSAGE_DELIVERY_STATUS_READ" = "Read"; + +/* State of a message if it failed to be sent */ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; + +/* Title of the message information screen describing the date/time a message was sent */ "MESSAGE_INFO_SENT" = "Sent"; + +/* Title of the message information screen describing the date/time a message was received on a specific device */ "MESSAGE_INFO_RECEIVED" = "Received"; + +/* Title of the message information screen describing the sender of the message */ "MESSAGE_INFO_FROM" = "From"; + +/* Title of the message information screen describing the identifier of the attachment */ "ATTACHMENT_INFO_FILE_ID" = "File ID"; + +/* Title of the message information screen describing the file type of the attachment */ "ATTACHMENT_INFO_FILE_TYPE" = "File Type"; + +/* Title of the message information screen describing the size of the attachment */ "ATTACHMENT_INFO_FILE_SIZE" = "File Size"; + +/* Title on the message information screen describing the resolution of a media attachment */ "ATTACHMENT_INFO_RESOLUTION" = "Resolution"; + +/* Title on the message information screen describing the duration of a media attachment */ "ATTACHMENT_INFO_DURATION" = "Duration"; + +/* State of a message after it failed to sync to the current users other devices */ "MESSAGE_DELIVERY_STATUS_FAILED_SYNC" = "Failed to sync"; + +/* State of a message while it's in the process of being synced to the users other devices */ "MESSAGE_DELIVERY_STATUS_SYNCING" = "Syncing"; + +/* Title of the modal that appears after a user taps on the state of a message which failed to send */ "MESSAGE_DELIVERY_FAILED_TITLE" = "Failed to send message"; + +/* Title of the modal that appears after a user taps on the state of a message which failed to sync to the users other devices */ "MESSAGE_DELIVERY_FAILED_SYNC_TITLE" = "Failed to sync message to your other devices"; + +/* Action for the modal shown when asking the user whether they want to delete from all of their devices */ "delete_message_for_me_and_my_devices" = "Delete from all of my devices"; + +/* Action in the long-press menu to trigger a message to be sent again after it has failed */ "context_menu_resend" = "Resend"; + +/* Action in the long-press menu to trigger a message to be synced again after it has failed */ "context_menu_resync" = "Resync"; + +/* Title of a modal show the first time a user tries to search for GIFs */ "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; + +/* Message of a modal show the first time a user tries to search for GIFs */ "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; + +/* Action in the long-press menu to view more information about a specific message */ "message_info_title" = "Message Info"; + +/* Action to mute a conversation in the swipe menu */ "mute_button_text" = "Mute"; + +/* Action in the swipe menu to unmute a conversation */ "unmute_button_text" = "Unmute"; + +/* Action in the swipe menu to mark a conversation as read */ +"MARK_AS_READ" = "Mark read"; + +/* Action in the swipe menu to mark a conversation as unread */ +"MARK_AS_UNREAD" = "Mark unread"; + +/* Title of the confirmation modal show when attempting to leave a group conversation */ "leave_group_confirmation_alert_title" = "Leave Group"; + +/* Title of the confirmation modal show when attempting to leave a community conversation */ "leave_community_confirmation_alert_title" = "Leave Community"; + +/* Message in the confirmation modal when leaving a community conversation */ "leave_community_confirmation_alert_message" = "Are you sure you want to leave %@?"; + +/* Conversation subtitle while the user in the process of leaving */ "group_you_leaving" = "Leaving..."; + +/* Conversation subtitle if the user in the failed to leave */ "group_leave_error" = "Failed to leave Group!"; + +/* Message within a conversation indicating the device was unable to leave a group conversation */ "group_unable_to_leave" = "Unable to leave the Group, please try again"; + +/* Title in the confirmation modal to delete a group */ "delete_group_confirmation_alert_title" = "Delete Group"; + +/* Message in the confirmation modal to delete a group */ "delete_group_confirmation_alert_message" = "Are you sure you want to delete %@?"; + +/* Title in the confirmation modal when the user tries to delete a one-to-one conversation */ "delete_conversation_confirmation_alert_title" = "Delete Conversation"; + +/* Message in the confirmation modal when the user tries to delete a one-to-one conversation */ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; + +/* Title in the confirmation modal when the user tries to hide the 'Note to Self' conversation */ "hide_note_to_self_confirmation_alert_title" = "Hide Note to Self"; + +/* Message in the confirmation modal when the user tries to hide the 'Note to Self' conversation */ "hide_note_to_self_confirmation_alert_message" = "Are you sure you want to hide %@?"; + +/* Title in the modal for updating the users profile display picture */ "update_profile_modal_title" = "Set Display Picture"; + +/* Save action in the modal for updating the users profile display picture */ "update_profile_modal_save" = "Save"; + +/* Remove action in the modal for updating the users profile display picture */ "update_profile_modal_remove" = "Remove"; + +/* Title for the error when failing to remove the users profile display picture */ "update_profile_modal_remove_error_title" = "Unable to remove avatar image"; + +/* Title for the error when the user selects a profile display picture that is too large */ "update_profile_modal_max_size_error_title" = "Maximum File Size Exceeded"; + +/* Message for the error when the user selects a profile display picture that is too large */ "update_profile_modal_max_size_error_message" = "Please select a smaller photo and try again"; + +/* Title for the error when the user fails to update their profile display picture */ "update_profile_modal_error_title" = "Couldn't Update Profile"; + +/* Message for the error when the user fails to update their profile display picture */ "update_profile_modal_error_message" = "Please check your internet connection and try again"; + +/* Placeholder when entering a nickname for a contact */ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; -"MARK_AS_READ" = "Mark Read"; -"MARK_AS_UNREAD" = "Mark Unread"; + +/* The separator within a conversation indicating that following messages are unread */ "UNREAD_MESSAGES" = "Unread Messages"; + +/* Empty state for a conversation */ "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; + +/* Empty state for a read-only conversation */ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; + +/* Empty state for the 'Note to Self' conversation */ "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; + +/* Message to indicate a user has Community Message Requests disabled */ "COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message."; + +/* Warning to indicate one of the users devices is running an old version of Session */ "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; + +/* Ann error displayed if the device is unable to retrieve the users recovery password */ "LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue."; + +/* An error displayed when trying to send a message if the device is unable to save it to the database */ "FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages."; + +/* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/eo.lproj/Localizable.strings b/Session/Meta/Translations/eo.lproj/Localizable.strings index 10b934227..c83b467de 100644 --- a/Session/Meta/Translations/eo.lproj/Localizable.strings +++ b/Session/Meta/Translations/eo.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/es-ES.lproj/Localizable.strings b/Session/Meta/Translations/es-ES.lproj/Localizable.strings index 5e8d0cba1..ef13b7ca3 100644 --- a/Session/Meta/Translations/es-ES.lproj/Localizable.strings +++ b/Session/Meta/Translations/es-ES.lproj/Localizable.strings @@ -781,3 +781,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 28999516d..56e29c303 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index 69b4ed23a..7b69052b8 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/fil.lproj/Localizable.strings b/Session/Meta/Translations/fil.lproj/Localizable.strings index cdc9482be..51cbe9a9f 100644 --- a/Session/Meta/Translations/fil.lproj/Localizable.strings +++ b/Session/Meta/Translations/fil.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 9ae7ad774..c9bc93b7e 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index 646e119f7..2546fbe24 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 80129af34..a88fcbc38 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/hu.lproj/Localizable.strings b/Session/Meta/Translations/hu.lproj/Localizable.strings index ac115f801..60a2bf828 100644 --- a/Session/Meta/Translations/hu.lproj/Localizable.strings +++ b/Session/Meta/Translations/hu.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/id.lproj/Localizable.strings b/Session/Meta/Translations/id.lproj/Localizable.strings index e06c4bffc..cd4a08e77 100644 --- a/Session/Meta/Translations/id.lproj/Localizable.strings +++ b/Session/Meta/Translations/id.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index 21b1f7030..b0ea3786d 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index b546bec81..f58d1d452 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/ko.lproj/Localizable.strings b/Session/Meta/Translations/ko.lproj/Localizable.strings index ac2cfcebc..f3b9a6cd7 100644 --- a/Session/Meta/Translations/ko.lproj/Localizable.strings +++ b/Session/Meta/Translations/ko.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/ku.lproj/Localizable.strings b/Session/Meta/Translations/ku.lproj/Localizable.strings index e576c2311..5174337ef 100644 --- a/Session/Meta/Translations/ku.lproj/Localizable.strings +++ b/Session/Meta/Translations/ku.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/lt.lproj/Localizable.strings b/Session/Meta/Translations/lt.lproj/Localizable.strings index 8f4c90ff2..384874741 100644 --- a/Session/Meta/Translations/lt.lproj/Localizable.strings +++ b/Session/Meta/Translations/lt.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/lv.lproj/Localizable.strings b/Session/Meta/Translations/lv.lproj/Localizable.strings index 8591dcaa3..d69ace952 100644 --- a/Session/Meta/Translations/lv.lproj/Localizable.strings +++ b/Session/Meta/Translations/lv.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/ne-NP.lproj/Localizable.strings b/Session/Meta/Translations/ne-NP.lproj/Localizable.strings index 291569f73..fbf90ed08 100644 --- a/Session/Meta/Translations/ne-NP.lproj/Localizable.strings +++ b/Session/Meta/Translations/ne-NP.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index fa642f706..66dcd486d 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/no.lproj/Localizable.strings b/Session/Meta/Translations/no.lproj/Localizable.strings index efe40c524..8b4fe12a2 100644 --- a/Session/Meta/Translations/no.lproj/Localizable.strings +++ b/Session/Meta/Translations/no.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index ea314af09..95d3083e3 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/pt-BR.lproj/Localizable.strings b/Session/Meta/Translations/pt-BR.lproj/Localizable.strings index db4be2506..fd1b75f46 100644 --- a/Session/Meta/Translations/pt-BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt-BR.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/pt-PT.lproj/Localizable.strings b/Session/Meta/Translations/pt-PT.lproj/Localizable.strings index 85a3b899c..631bdaa20 100644 --- a/Session/Meta/Translations/pt-PT.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt-PT.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/ro.lproj/Localizable.strings b/Session/Meta/Translations/ro.lproj/Localizable.strings index eae4d6ffb..392f155d2 100644 --- a/Session/Meta/Translations/ro.lproj/Localizable.strings +++ b/Session/Meta/Translations/ro.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 1ac9b632b..2074f54c5 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/si-LK.lproj/Localizable.strings b/Session/Meta/Translations/si-LK.lproj/Localizable.strings index 5fa6e7023..9458e8e7f 100644 --- a/Session/Meta/Translations/si-LK.lproj/Localizable.strings +++ b/Session/Meta/Translations/si-LK.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 189eae340..c050c21e9 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/sl.lproj/Localizable.strings b/Session/Meta/Translations/sl.lproj/Localizable.strings index 0fe2d7048..961c0a394 100644 --- a/Session/Meta/Translations/sl.lproj/Localizable.strings +++ b/Session/Meta/Translations/sl.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/sv-SE.lproj/Localizable.strings b/Session/Meta/Translations/sv-SE.lproj/Localizable.strings index 9c04606ce..c228a10bf 100644 --- a/Session/Meta/Translations/sv-SE.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv-SE.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 9d12afcec..9aaa76f81 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/tr.lproj/Localizable.strings b/Session/Meta/Translations/tr.lproj/Localizable.strings index deed7af56..fdd2724f0 100644 --- a/Session/Meta/Translations/tr.lproj/Localizable.strings +++ b/Session/Meta/Translations/tr.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/uk.lproj/Localizable.strings b/Session/Meta/Translations/uk.lproj/Localizable.strings index b9356cc1d..944382381 100644 --- a/Session/Meta/Translations/uk.lproj/Localizable.strings +++ b/Session/Meta/Translations/uk.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/vi.lproj/Localizable.strings b/Session/Meta/Translations/vi.lproj/Localizable.strings index 56e42ea1e..4ac12104f 100644 --- a/Session/Meta/Translations/vi.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/zh-CN.lproj/Localizable.strings b/Session/Meta/Translations/zh-CN.lproj/Localizable.strings index d41a51f94..e97895da4 100644 --- a/Session/Meta/Translations/zh-CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-CN.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; diff --git a/Session/Meta/Translations/zh-TW.lproj/Localizable.strings b/Session/Meta/Translations/zh-TW.lproj/Localizable.strings index 68dcbdc98..c07dd4e52 100644 --- a/Session/Meta/Translations/zh-TW.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-TW.lproj/Localizable.strings @@ -780,3 +780,30 @@ /* An error indicating that the device was unable to access the database for some reason */ "database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; + +/* A message indicating how the disappearing messages setting applies in a one-to-one conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_CONTACTS" = "This setting applies to everyone in this conversation."; + +/* A message indicating how the disappearing messages setting applies in a group conversation */ +"DISAPPERING_MESSAGES_SUBTITLE_GROUPS" = "Messages disappear after they have been sent."; + +/* A record that appears within the message history to indicate that the current user turned on disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_ENABLE" = "You have set messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user update the disappearing messages setting */ +"YOU_DISAPPEARING_MESSAGES_INFO_UPDATE" = "You have changed messages to disappear %@ after they have been %@"; + +/* A record that appears within the message history to indicate that the current user has disabled disappearing messages */ +"YOU_DISAPPEARING_MESSAGES_INFO_DISABLE" = "You have turned off disappearing messages"; + +/* The title for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_TITLE" = "Legacy"; + +/* The description for the legacy type of disappearing messages on the disappearing messages configuration screen */ +"DISAPPEARING_MESSAGES_TYPE_LEGACY_DESCRIPTION" = "Original version of disappearing messages."; + +/* A warning shown at the top of a conversation to indicate a participant is using an old version of Session which may not support the updated disappearing messages functionality */ +"DISAPPEARING_MESSAGES_OUTDATED_CLIENT_BANNER" = "%@ is using an outdated client. Disappearing messages may not work as expected."; + +/* An error which can occur when a user tries to update from a version that Session no longer supports updating from */ +"DATABASE_UNSUPPORTED_MIGRATION" = "You are trying to updated from a version which no longer supports upgrading\n\nIn order to continue to use session you need to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks";