You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
663 lines
24 KiB
663 lines
24 KiB
import Foundation
|
|
import UIKit
|
|
|
|
// MARK: - vCard版本转换工具
|
|
struct VCardConverter {
|
|
|
|
/// 将vCard 2.1格式转换为3.0格式
|
|
static func convertVCard21To30(_ vcard21: String) -> String {
|
|
let lines = vcard21.components(separatedBy: .newlines)
|
|
var vcard30 = "BEGIN:VCARD\nVERSION:3.0\n"
|
|
|
|
for line in lines {
|
|
let trimmedLine = line.trimmingCharacters(in: .whitespaces)
|
|
if trimmedLine.isEmpty || trimmedLine.hasPrefix("BEGIN:") || trimmedLine.hasPrefix("VERSION:") || trimmedLine.hasPrefix("END:") {
|
|
continue
|
|
}
|
|
|
|
// 处理N字段 (姓名)
|
|
if trimmedLine.hasPrefix("N:") {
|
|
let nameValue = String(trimmedLine.dropFirst(2))
|
|
let nameParts = nameValue.components(separatedBy: ";")
|
|
if nameParts.count >= 2 {
|
|
let lastName = nameParts[0]
|
|
let firstName = nameParts[1]
|
|
vcard30 += "N:\(lastName);\(firstName);;;\n"
|
|
vcard30 += "FN:\(firstName) \(lastName)\n"
|
|
}
|
|
}
|
|
// 处理TEL字段 (电话)
|
|
else if trimmedLine.hasPrefix("TEL") {
|
|
let telValue = String(trimmedLine.dropFirst(3))
|
|
if telValue.hasPrefix(";TYPE=") {
|
|
let typeStart = telValue.firstIndex(of: "=")!
|
|
let typeEnd = telValue.firstIndex(of: ":") ?? telValue.endIndex
|
|
let type = String(telValue[telValue.index(after: typeStart)..<typeEnd])
|
|
let number = String(telValue[telValue.index(after: typeEnd)...])
|
|
vcard30 += "TEL;TYPE=\(type):\(number)\n"
|
|
} else if telValue.hasPrefix(":") {
|
|
let number = String(telValue.dropFirst())
|
|
vcard30 += "TEL:\(number)\n"
|
|
}
|
|
}
|
|
// 处理EMAIL字段 (邮箱)
|
|
else if trimmedLine.hasPrefix("EMAIL") {
|
|
let emailValue = String(trimmedLine.dropFirst(5))
|
|
if emailValue.hasPrefix(";TYPE=") {
|
|
let typeStart = emailValue.firstIndex(of: "=")!
|
|
let typeEnd = emailValue.firstIndex(of: ":") ?? emailValue.endIndex
|
|
let type = String(emailValue[emailValue.index(after: typeStart)..<typeEnd])
|
|
let email = String(emailValue[emailValue.index(after: typeEnd)...])
|
|
vcard30 += "EMAIL;TYPE=\(type):\(email)\n"
|
|
} else if emailValue.hasPrefix(":") {
|
|
let email = String(emailValue.dropFirst())
|
|
vcard30 += "EMAIL:\(email)\n"
|
|
}
|
|
}
|
|
// 处理ADR字段 (地址)
|
|
else if trimmedLine.hasPrefix("ADR") {
|
|
let adrValue = String(trimmedLine.dropFirst(3))
|
|
if adrValue.hasPrefix(";TYPE=") {
|
|
let typeStart = adrValue.firstIndex(of: "=")!
|
|
let typeEnd = adrValue.firstIndex(of: ":") ?? adrValue.endIndex
|
|
let type = String(adrValue[adrValue.index(after: typeStart)..<typeEnd])
|
|
let address = String(adrValue[adrValue.index(after: typeEnd)...])
|
|
vcard30 += "ADR;TYPE=\(type):\(address)\n"
|
|
} else if adrValue.hasPrefix(":") {
|
|
let address = String(adrValue.dropFirst())
|
|
vcard30 += "ADR:\(address)\n"
|
|
}
|
|
}
|
|
// 处理其他字段
|
|
else if trimmedLine.contains(":") {
|
|
let colonIndex = trimmedLine.firstIndex(of: ":")!
|
|
let fieldName = String(trimmedLine[..<colonIndex])
|
|
let fieldValue = String(trimmedLine[trimmedLine.index(after: colonIndex)...])
|
|
|
|
// 跳过已处理的字段
|
|
if !["N", "TEL", "EMAIL", "ADR"].contains(fieldName) {
|
|
vcard30 += "\(fieldName):\(fieldValue)\n"
|
|
}
|
|
}
|
|
}
|
|
|
|
vcard30 += "END:VCARD"
|
|
return vcard30
|
|
}
|
|
|
|
/// 检测vCard版本
|
|
static func detectVCardVersion(_ vcard: String) -> String {
|
|
let lines = vcard.components(separatedBy: .newlines)
|
|
for line in lines {
|
|
if line.hasPrefix("VERSION:") {
|
|
return String(line.dropFirst(8))
|
|
}
|
|
}
|
|
return "3.0" // 默认版本
|
|
}
|
|
|
|
/// 标准化vCard为3.0版本
|
|
static func normalizeVCard(_ vcard: String) -> String {
|
|
let version = detectVCardVersion(vcard)
|
|
if version == "2.1" {
|
|
return convertVCard21To30(vcard)
|
|
}
|
|
return vcard
|
|
}
|
|
|
|
/// 测试vCard版本转换功能
|
|
static func testVCardConversion() {
|
|
let vcard21 = """
|
|
BEGIN:VCARD
|
|
VERSION:2.1
|
|
N:Surname;Givenname;;;
|
|
FN:Givenname Surname
|
|
TEL;TYPE=WORK:123-456-7890
|
|
EMAIL;TYPE=PREF:test@example.com
|
|
END:VCARD
|
|
"""
|
|
|
|
let converted = convertVCard21To30(vcard21)
|
|
print("Original vCard 2.1:")
|
|
print(vcard21)
|
|
print("\nConverted to vCard 3.0:")
|
|
print(converted)
|
|
}
|
|
}
|
|
|
|
// MARK: - 二维码解析器
|
|
class QRCodeParser {
|
|
|
|
// MARK: - 解析二维码内容
|
|
static func parseQRCode(_ content: String) -> ParsedQRData {
|
|
let trimmedContent = content.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
// Wi-Fi
|
|
if trimmedContent.hasPrefix("WIFI:") {
|
|
return parseWiFi(trimmedContent)
|
|
}
|
|
|
|
// Email
|
|
if trimmedContent.hasPrefix("mailto:") {
|
|
return parseEmail(trimmedContent)
|
|
}
|
|
|
|
// Phone
|
|
if trimmedContent.hasPrefix("tel:") {
|
|
return parsePhone(trimmedContent)
|
|
}
|
|
|
|
// SMS
|
|
if trimmedContent.hasPrefix("SMSTO:") {
|
|
return parseSMS(trimmedContent)
|
|
}
|
|
|
|
// vCard
|
|
if trimmedContent.hasPrefix("BEGIN:VCARD") {
|
|
return parseVCard(trimmedContent)
|
|
}
|
|
|
|
// MeCard
|
|
if trimmedContent.hasPrefix("MECARD:") {
|
|
return parseMeCard(trimmedContent)
|
|
}
|
|
|
|
// Calendar
|
|
if trimmedContent.hasPrefix("BEGIN:VEVENT") {
|
|
return parseCalendar(trimmedContent)
|
|
}
|
|
|
|
// Instagram
|
|
if trimmedContent.hasPrefix("instagram://user?username=") {
|
|
return parseInstagram(trimmedContent)
|
|
}
|
|
|
|
// Facebook
|
|
if trimmedContent.hasPrefix("fb://profile/") {
|
|
return parseFacebook(trimmedContent)
|
|
}
|
|
|
|
// Spotify
|
|
if trimmedContent.hasPrefix("spotify:") {
|
|
return parseSpotify(trimmedContent)
|
|
}
|
|
|
|
// X (Twitter)
|
|
if trimmedContent.hasPrefix("twitter://user?screen_name=") || trimmedContent.contains("x.com") || trimmedContent.contains("twitter.com") {
|
|
return parseTwitter(trimmedContent)
|
|
}
|
|
|
|
// WhatsApp
|
|
if trimmedContent.hasPrefix("whatsapp://send?phone=") {
|
|
return parseWhatsApp(trimmedContent)
|
|
}
|
|
|
|
// Viber
|
|
if trimmedContent.hasPrefix("viber://add?number=") {
|
|
return parseViber(trimmedContent)
|
|
}
|
|
|
|
// Snapchat
|
|
if trimmedContent.hasPrefix("snapchat://") {
|
|
return parseSnapchat(trimmedContent)
|
|
}
|
|
|
|
// TikTok
|
|
if trimmedContent.contains("tiktok.com") || trimmedContent.contains("www.tiktok.com") {
|
|
return parseTikTok(trimmedContent)
|
|
}
|
|
|
|
// URL (检查是否为有效的URL)
|
|
if isValidURL(trimmedContent) {
|
|
return parseURL(trimmedContent)
|
|
}
|
|
|
|
// Location
|
|
if trimmedContent.hasPrefix("geo:") {
|
|
return parseLocation(trimmedContent)
|
|
}
|
|
|
|
// 默认为文本类型
|
|
return ParsedQRData(
|
|
type: .text,
|
|
title: "文本信息",
|
|
subtitle: trimmedContent.count > 50 ? String(trimmedContent.prefix(50)) + "..." : trimmedContent,
|
|
icon: "text.quote"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析Wi-Fi
|
|
private static func parseWiFi(_ content: String) -> ParsedQRData {
|
|
let wifiInfo = content.replacingOccurrences(of: "WIFI:", with: "")
|
|
let components = wifiInfo.components(separatedBy: ";")
|
|
|
|
var ssid = ""
|
|
var password = ""
|
|
var encryption = "WPA"
|
|
|
|
for component in components {
|
|
if component.hasPrefix("S:") {
|
|
ssid = String(component.dropFirst(2))
|
|
} else if component.hasPrefix("P:") {
|
|
password = String(component.dropFirst(2))
|
|
} else if component.hasPrefix("T:") {
|
|
encryption = String(component.dropFirst(2))
|
|
}
|
|
}
|
|
|
|
let title = "Wi-Fi网络"
|
|
let subtitle = "网络名称: \(ssid)\n加密类型: \(encryption)\n密码: \(password.isEmpty ? "无" : "已设置")"
|
|
|
|
return ParsedQRData(
|
|
type: .wifi,
|
|
title: title,
|
|
subtitle: subtitle,
|
|
icon: "wifi"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析Email
|
|
private static func parseEmail(_ content: String) -> ParsedQRData {
|
|
let email = content.replacingOccurrences(of: "mailto:", with: "")
|
|
|
|
return ParsedQRData(
|
|
type: .mail,
|
|
title: "邮箱地址",
|
|
subtitle: email,
|
|
icon: "envelope"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析Phone
|
|
private static func parsePhone(_ content: String) -> ParsedQRData {
|
|
let phone = content.replacingOccurrences(of: "tel:", with: "")
|
|
|
|
return ParsedQRData(
|
|
type: .phone,
|
|
title: "电话号码",
|
|
subtitle: phone,
|
|
icon: "phone"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析SMS
|
|
private static func parseSMS(_ content: String) -> ParsedQRData {
|
|
let smsInfo = content.replacingOccurrences(of: "SMSTO:", with: "")
|
|
let components = smsInfo.components(separatedBy: ":")
|
|
|
|
let phone = components.first ?? ""
|
|
let message = components.count > 1 ? components[1] : ""
|
|
|
|
let title = "短信"
|
|
let subtitle = "号码: \(phone)\n内容: \(message)"
|
|
|
|
return ParsedQRData(
|
|
type: .sms,
|
|
title: title,
|
|
subtitle: subtitle,
|
|
icon: "message"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析vCard
|
|
private static func parseVCard(_ content: String) -> ParsedQRData {
|
|
// 标准化vCard为3.0版本
|
|
let normalizedVCard = VCardConverter.normalizeVCard(content)
|
|
let lines = normalizedVCard.components(separatedBy: .newlines)
|
|
|
|
var name = ""
|
|
var phone = ""
|
|
var email = ""
|
|
var company = ""
|
|
var title = ""
|
|
var address = ""
|
|
var website = ""
|
|
|
|
for line in lines {
|
|
let trimmedLine = line.trimmingCharacters(in: .whitespaces)
|
|
if trimmedLine.hasPrefix("FN:") {
|
|
name = String(trimmedLine.dropFirst(3))
|
|
} else if trimmedLine.hasPrefix("TEL") {
|
|
let telValue = String(trimmedLine.dropFirst(3))
|
|
if telValue.contains(":") {
|
|
let number = telValue.components(separatedBy: ":").last ?? ""
|
|
phone = number
|
|
}
|
|
} else if trimmedLine.hasPrefix("EMAIL") {
|
|
let emailValue = String(trimmedLine.dropFirst(5))
|
|
if emailValue.contains(":") {
|
|
let emailAddress = emailValue.components(separatedBy: ":").last ?? ""
|
|
email = emailAddress
|
|
}
|
|
} else if trimmedLine.hasPrefix("ORG:") {
|
|
company = String(trimmedLine.dropFirst(4))
|
|
} else if trimmedLine.hasPrefix("TITLE:") {
|
|
title = String(trimmedLine.dropFirst(6))
|
|
} else if trimmedLine.hasPrefix("ADR") {
|
|
let adrValue = String(trimmedLine.dropFirst(3))
|
|
if adrValue.contains(":") {
|
|
let addressParts = adrValue.components(separatedBy: ":")
|
|
if addressParts.count > 1 {
|
|
let addressComponents = addressParts[1].components(separatedBy: ";")
|
|
if addressComponents.count >= 3 {
|
|
address = "\(addressComponents[2]) \(addressComponents[1])"
|
|
}
|
|
}
|
|
}
|
|
} else if trimmedLine.hasPrefix("URL:") {
|
|
website = String(trimmedLine.dropFirst(4))
|
|
}
|
|
}
|
|
|
|
var subtitle = ""
|
|
if !name.isEmpty { subtitle += "姓名: \(name)\n" }
|
|
if !phone.isEmpty { subtitle += "电话: \(phone)\n" }
|
|
if !email.isEmpty { subtitle += "邮箱: \(email)\n" }
|
|
if !company.isEmpty { subtitle += "公司: \(company)\n" }
|
|
if !title.isEmpty { subtitle += "职位: \(title)\n" }
|
|
if !address.isEmpty { subtitle += "地址: \(address)\n" }
|
|
if !website.isEmpty { subtitle += "网站: \(website)\n" }
|
|
|
|
// 移除最后一个换行符
|
|
if subtitle.hasSuffix("\n") {
|
|
subtitle = String(subtitle.dropLast())
|
|
}
|
|
|
|
return ParsedQRData(
|
|
type: .vcard,
|
|
title: "联系人信息",
|
|
subtitle: subtitle,
|
|
icon: "person.crop.rectangle"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析MeCard
|
|
private static func parseMeCard(_ content: String) -> ParsedQRData {
|
|
let mecardInfo = content.replacingOccurrences(of: "MECARD:", with: "")
|
|
let components = mecardInfo.components(separatedBy: ";")
|
|
|
|
var name = ""
|
|
var nickname = ""
|
|
var phone = ""
|
|
var email = ""
|
|
var company = ""
|
|
var address = ""
|
|
var website = ""
|
|
var birthday = ""
|
|
var note = ""
|
|
|
|
for component in components {
|
|
let trimmedComponent = component.trimmingCharacters(in: .whitespaces)
|
|
if trimmedComponent.isEmpty { continue }
|
|
|
|
if trimmedComponent.hasPrefix("N:") {
|
|
let nameValue = String(trimmedComponent.dropFirst(2))
|
|
let nameParts = nameValue.components(separatedBy: ",")
|
|
if nameParts.count >= 2 {
|
|
let lastName = nameParts[0]
|
|
let firstName = nameParts[1]
|
|
name = "\(firstName) \(lastName)"
|
|
} else if nameParts.count == 1 {
|
|
name = nameParts[0]
|
|
}
|
|
} else if trimmedComponent.hasPrefix("NICKNAME:") {
|
|
nickname = String(trimmedComponent.dropFirst(9))
|
|
} else if trimmedComponent.hasPrefix("TEL:") {
|
|
phone = String(trimmedComponent.dropFirst(4))
|
|
} else if trimmedComponent.hasPrefix("EMAIL:") {
|
|
email = String(trimmedComponent.dropFirst(6))
|
|
} else if trimmedComponent.hasPrefix("ORG:") {
|
|
company = String(trimmedComponent.dropFirst(4))
|
|
} else if trimmedComponent.hasPrefix("ADR:") {
|
|
address = String(trimmedComponent.dropFirst(4))
|
|
} else if trimmedComponent.hasPrefix("URL:") {
|
|
website = String(trimmedComponent.dropFirst(4))
|
|
} else if trimmedComponent.hasPrefix("BDAY:") {
|
|
let birthdayValue = String(trimmedComponent.dropFirst(5))
|
|
if birthdayValue.count == 8 {
|
|
let year = String(birthdayValue.prefix(4))
|
|
let month = String(birthdayValue.dropFirst(4).prefix(2))
|
|
let day = String(birthdayValue.dropFirst(6))
|
|
birthday = "\(year)年\(month)月\(day)日"
|
|
} else {
|
|
birthday = birthdayValue
|
|
}
|
|
} else if trimmedComponent.hasPrefix("NOTE:") {
|
|
note = String(trimmedComponent.dropFirst(5))
|
|
}
|
|
}
|
|
|
|
var subtitle = ""
|
|
if !name.isEmpty { subtitle += "姓名: \(name)\n" }
|
|
if !nickname.isEmpty { subtitle += "昵称: \(nickname)\n" }
|
|
if !phone.isEmpty { subtitle += "电话: \(phone)\n" }
|
|
if !email.isEmpty { subtitle += "邮箱: \(email)\n" }
|
|
if !company.isEmpty { subtitle += "公司: \(company)\n" }
|
|
if !address.isEmpty { subtitle += "地址: \(address)\n" }
|
|
if !website.isEmpty { subtitle += "网站: \(website)\n" }
|
|
if !birthday.isEmpty { subtitle += "生日: \(birthday)\n" }
|
|
if !note.isEmpty { subtitle += "备注: \(note)\n" }
|
|
|
|
// 移除最后一个换行符
|
|
if subtitle.hasSuffix("\n") {
|
|
subtitle = String(subtitle.dropLast())
|
|
}
|
|
|
|
return ParsedQRData(
|
|
type: .mecard,
|
|
title: "联系人信息",
|
|
subtitle: subtitle,
|
|
icon: "person.crop.rectangle"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析Calendar
|
|
private static func parseCalendar(_ content: String) -> ParsedQRData {
|
|
let lines = content.components(separatedBy: .newlines)
|
|
var summary = ""
|
|
var startTime = ""
|
|
var endTime = ""
|
|
var location = ""
|
|
var description = ""
|
|
|
|
for line in lines {
|
|
if line.hasPrefix("SUMMARY:") {
|
|
summary = String(line.dropFirst(8))
|
|
} else if line.hasPrefix("DTSTART:") {
|
|
startTime = String(line.dropFirst(8))
|
|
} else if line.hasPrefix("DTEND:") {
|
|
endTime = String(line.dropFirst(6))
|
|
} else if line.hasPrefix("LOCATION:") {
|
|
location = String(line.dropFirst(9))
|
|
} else if line.hasPrefix("DESCRIPTION:") {
|
|
description = String(line.dropFirst(12))
|
|
}
|
|
}
|
|
|
|
// 格式化时间显示
|
|
let formattedStartTime = formatCalendarTime(startTime)
|
|
let formattedEndTime = formatCalendarTime(endTime)
|
|
|
|
let title = "日历事件"
|
|
var subtitle = "事件: \(summary)\n开始: \(formattedStartTime)\n结束: \(formattedEndTime)"
|
|
if !location.isEmpty {
|
|
subtitle += "\n地点: \(location)"
|
|
}
|
|
if !description.isEmpty {
|
|
subtitle += "\n描述: \(description)"
|
|
}
|
|
|
|
return ParsedQRData(
|
|
type: .calendar,
|
|
title: title,
|
|
subtitle: subtitle,
|
|
icon: "calendar"
|
|
)
|
|
}
|
|
|
|
// MARK: - 格式化日历时间
|
|
private static func formatCalendarTime(_ timeString: String) -> String {
|
|
guard timeString.count >= 15 else { return timeString }
|
|
|
|
let dateFormatter = DateFormatter()
|
|
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss"
|
|
|
|
if let date = dateFormatter.date(from: timeString) {
|
|
let displayFormatter = DateFormatter()
|
|
displayFormatter.dateStyle = .medium
|
|
displayFormatter.timeStyle = .short
|
|
return displayFormatter.string(from: date)
|
|
}
|
|
|
|
return timeString
|
|
}
|
|
|
|
// MARK: - 解析Instagram
|
|
private static func parseInstagram(_ content: String) -> ParsedQRData {
|
|
let username = content.replacingOccurrences(of: "instagram://user?username=", with: "")
|
|
|
|
return ParsedQRData(
|
|
type: .instagram,
|
|
title: "Instagram",
|
|
subtitle: "用户名: \(username)",
|
|
icon: "camera"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析Facebook
|
|
private static func parseFacebook(_ content: String) -> ParsedQRData {
|
|
let profileId = content.replacingOccurrences(of: "fb://profile/", with: "")
|
|
|
|
return ParsedQRData(
|
|
type: .facebook,
|
|
title: "Facebook",
|
|
subtitle: "用户ID: \(profileId)",
|
|
icon: "person.2"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析Spotify
|
|
private static func parseSpotify(_ content: String) -> ParsedQRData {
|
|
let trackId = content.replacingOccurrences(of: "spotify:track:", with: "")
|
|
|
|
return ParsedQRData(
|
|
type: .spotify,
|
|
title: "Spotify",
|
|
subtitle: "曲目ID: \(trackId)",
|
|
icon: "music.note"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析X (Twitter)
|
|
private static func parseTwitter(_ content: String) -> ParsedQRData {
|
|
var username = ""
|
|
|
|
if content.hasPrefix("twitter://user?screen_name=") {
|
|
username = content.replacingOccurrences(of: "twitter://user?screen_name=", with: "")
|
|
} else if content.contains("x.com") || content.contains("twitter.com") {
|
|
username = content.components(separatedBy: "/").dropLast().last ?? ""
|
|
}
|
|
|
|
return ParsedQRData(
|
|
type: .twitter,
|
|
title: "X",
|
|
subtitle: "用户名: \(username)",
|
|
icon: "bird"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析WhatsApp
|
|
private static func parseWhatsApp(_ content: String) -> ParsedQRData {
|
|
let phone = content.replacingOccurrences(of: "whatsapp://send?phone=", with: "")
|
|
|
|
return ParsedQRData(
|
|
type: .whatsapp,
|
|
title: "WhatsApp",
|
|
subtitle: "电话号码: \(phone)",
|
|
icon: "message.circle"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析Viber
|
|
private static func parseViber(_ content: String) -> ParsedQRData {
|
|
let phone = content.replacingOccurrences(of: "viber://add?number=", with: "")
|
|
|
|
return ParsedQRData(
|
|
type: .viber,
|
|
title: "Viber",
|
|
subtitle: "电话号码: \(phone)",
|
|
icon: "bubble.left.and.bubble.right"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析Snapchat
|
|
private static func parseSnapchat(_ content: String) -> ParsedQRData {
|
|
let username = content.replacingOccurrences(of: "snapchat://", with: "")
|
|
|
|
return ParsedQRData(
|
|
type: .snapchat,
|
|
title: "Snapchat",
|
|
subtitle: "用户名: \(username)",
|
|
icon: "camera.viewfinder"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析TikTok
|
|
private static func parseTikTok(_ content: String) -> ParsedQRData {
|
|
var username = ""
|
|
|
|
if content.contains("www.tiktok.com") {
|
|
// 处理 https://www.tiktok.com/@username 格式
|
|
if let atIndex = content.lastIndex(of: "@") {
|
|
username = String(content[content.index(after: atIndex)...])
|
|
}
|
|
} else if content.contains("tiktok.com") {
|
|
// 处理 https://tiktok.com/@username 格式
|
|
if let atIndex = content.lastIndex(of: "@") {
|
|
username = String(content[content.index(after: atIndex)...])
|
|
}
|
|
}
|
|
|
|
return ParsedQRData(
|
|
type: .tiktok,
|
|
title: "TikTok",
|
|
subtitle: "用户名: \(username)",
|
|
icon: "music.mic"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析URL
|
|
private static func parseURL(_ content: String) -> ParsedQRData {
|
|
return ParsedQRData(
|
|
type: .url,
|
|
title: "网址链接",
|
|
subtitle: content,
|
|
icon: "link"
|
|
)
|
|
}
|
|
|
|
// MARK: - 解析Location
|
|
private static func parseLocation(_ content: String) -> ParsedQRData {
|
|
let coordinates = content.replacingOccurrences(of: "geo:", with: "")
|
|
let coords = coordinates.components(separatedBy: ",")
|
|
|
|
let latitude = coords.first ?? ""
|
|
let longitude = coords.count > 1 ? coords[1] : ""
|
|
|
|
let title = "地理位置"
|
|
let subtitle = "纬度: \(latitude)\n经度: \(longitude)"
|
|
|
|
return ParsedQRData(
|
|
type: .location,
|
|
title: title,
|
|
subtitle: subtitle,
|
|
icon: "location"
|
|
)
|
|
}
|
|
|
|
// MARK: - 验证URL
|
|
private static func isValidURL(_ string: String) -> Bool {
|
|
guard let url = URL(string: string) else { return false }
|
|
return UIApplication.shared.canOpenURL(url)
|
|
}
|
|
} |