237 lines
7.9 KiB
Dart
237 lines
7.9 KiB
Dart
import '../models/service_info.dart';
|
|
import '../data/service_data_repository.dart';
|
|
import '../data/legacy_service_data.dart';
|
|
import 'category_mapper_service.dart';
|
|
|
|
/// URL 매칭 관련 기능을 제공하는 서비스 클래스
|
|
class UrlMatcherService {
|
|
final ServiceDataRepository _dataRepository;
|
|
final CategoryMapperService _categoryMapper;
|
|
|
|
UrlMatcherService(this._dataRepository, this._categoryMapper);
|
|
|
|
/// 도메인 추출 (www와 TLD 제외)
|
|
String? extractDomain(String url) {
|
|
try {
|
|
final uri = Uri.parse(url);
|
|
final host = uri.host.toLowerCase();
|
|
|
|
// 도메인 부분 추출
|
|
var parts = host.split('.');
|
|
|
|
// www 제거
|
|
if (parts.isNotEmpty && parts[0] == 'www') {
|
|
parts = parts.sublist(1);
|
|
}
|
|
|
|
// 서브도메인 처리 (예: music.youtube.com)
|
|
if (parts.length >= 3) {
|
|
// 서브도메인 포함 전체 도메인 반환
|
|
return parts.sublist(0, parts.length - 1).join('.');
|
|
} else if (parts.length >= 2) {
|
|
// 메인 도메인만 반환
|
|
return parts[0];
|
|
}
|
|
|
|
return null;
|
|
} catch (e) {
|
|
print('UrlMatcherService: 도메인 추출 실패 - $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// URL로 서비스 찾기
|
|
Future<ServiceInfo?> findServiceByUrl(String url) async {
|
|
final domain = extractDomain(url);
|
|
if (domain == null) return null;
|
|
|
|
// JSON 데이터가 있으면 JSON에서 찾기
|
|
final servicesData = _dataRepository.getServicesData();
|
|
if (servicesData != null) {
|
|
final categories = servicesData['categories'] as Map<String, dynamic>;
|
|
|
|
for (final categoryEntry in categories.entries) {
|
|
final categoryId = categoryEntry.key;
|
|
final categoryData = categoryEntry.value as Map<String, dynamic>;
|
|
final services = categoryData['services'] as Map<String, dynamic>;
|
|
|
|
for (final serviceEntry in services.entries) {
|
|
final serviceId = serviceEntry.key;
|
|
final serviceData = serviceEntry.value as Map<String, dynamic>;
|
|
final domains = List<String>.from(serviceData['domains'] ?? []);
|
|
|
|
// 도메인이 일치하는지 확인
|
|
for (final serviceDomain in domains) {
|
|
if (domain.contains(serviceDomain) ||
|
|
serviceDomain.contains(domain)) {
|
|
final names = List<String>.from(serviceData['names'] ?? []);
|
|
final urls = serviceData['urls'] as Map<String, dynamic>?;
|
|
|
|
return ServiceInfo(
|
|
serviceId: serviceId,
|
|
serviceName: names.isNotEmpty ? names[0] : serviceId,
|
|
serviceUrl: urls?['kr'] ?? urls?['en'],
|
|
cancellationUrl: null,
|
|
categoryId: _categoryMapper.getCategoryIdByKey(categoryId),
|
|
categoryNameKr: categoryData['nameKr'] ?? '',
|
|
categoryNameEn: categoryData['nameEn'] ?? '',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// JSON에서 못 찾았으면 레거시 방식으로 찾기
|
|
for (final entry in LegacyServiceData.allServices.entries) {
|
|
final serviceUrl = entry.value;
|
|
final serviceDomain = extractDomain(serviceUrl);
|
|
|
|
if (serviceDomain != null &&
|
|
(domain.contains(serviceDomain) || serviceDomain.contains(domain))) {
|
|
return ServiceInfo(
|
|
serviceId: entry.key,
|
|
serviceName: entry.key,
|
|
serviceUrl: serviceUrl,
|
|
cancellationUrl: null,
|
|
categoryId: _categoryMapper.getCategoryForLegacyService(entry.key),
|
|
categoryNameKr: '',
|
|
categoryNameEn: '',
|
|
);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// 서비스명으로 URL 찾기
|
|
String? suggestUrl(String serviceName) {
|
|
if (serviceName.isEmpty) {
|
|
print('UrlMatcherService: 빈 serviceName');
|
|
return null;
|
|
}
|
|
|
|
// 소문자로 변환하여 비교
|
|
final lowerName = serviceName.toLowerCase().trim();
|
|
|
|
try {
|
|
// 정확한 매칭을 먼저 시도
|
|
for (final entry in LegacyServiceData.allServices.entries) {
|
|
if (lowerName.contains(entry.key.toLowerCase())) {
|
|
print('UrlMatcherService: 정확한 매칭 - $lowerName -> ${entry.key}');
|
|
return entry.value;
|
|
}
|
|
}
|
|
|
|
// OTT 서비스 검사
|
|
for (final entry in LegacyServiceData.ottServices.entries) {
|
|
if (lowerName.contains(entry.key.toLowerCase())) {
|
|
print('UrlMatcherService: OTT 서비스 매칭 - $lowerName -> ${entry.key}');
|
|
return entry.value;
|
|
}
|
|
}
|
|
|
|
// 음악 서비스 검사
|
|
for (final entry in LegacyServiceData.musicServices.entries) {
|
|
if (lowerName.contains(entry.key.toLowerCase())) {
|
|
print('UrlMatcherService: 음악 서비스 매칭 - $lowerName -> ${entry.key}');
|
|
return entry.value;
|
|
}
|
|
}
|
|
|
|
// AI 서비스 검사
|
|
for (final entry in LegacyServiceData.aiServices.entries) {
|
|
if (lowerName.contains(entry.key.toLowerCase())) {
|
|
print('UrlMatcherService: AI 서비스 매칭 - $lowerName -> ${entry.key}');
|
|
return entry.value;
|
|
}
|
|
}
|
|
|
|
// 프로그래밍 서비스 검사
|
|
for (final entry in LegacyServiceData.programmingServices.entries) {
|
|
if (lowerName.contains(entry.key.toLowerCase())) {
|
|
print('UrlMatcherService: 프로그래밍 서비스 매칭 - $lowerName -> ${entry.key}');
|
|
return entry.value;
|
|
}
|
|
}
|
|
|
|
// 오피스 툴 검사
|
|
for (final entry in LegacyServiceData.officeTools.entries) {
|
|
if (lowerName.contains(entry.key.toLowerCase())) {
|
|
print('UrlMatcherService: 오피스 툴 매칭 - $lowerName -> ${entry.key}');
|
|
return entry.value;
|
|
}
|
|
}
|
|
|
|
// 기타 서비스 검사
|
|
for (final entry in LegacyServiceData.otherServices.entries) {
|
|
if (lowerName.contains(entry.key.toLowerCase())) {
|
|
print('UrlMatcherService: 기타 서비스 매칭 - $lowerName -> ${entry.key}');
|
|
return entry.value;
|
|
}
|
|
}
|
|
|
|
// 전체 서비스에서 부분 매칭 재시도
|
|
for (final entry in LegacyServiceData.allServices.entries) {
|
|
final key = entry.key.toLowerCase();
|
|
if (key.contains(lowerName) || lowerName.contains(key)) {
|
|
print('UrlMatcherService: 부분 매칭 - $lowerName -> ${entry.key}');
|
|
return entry.value;
|
|
}
|
|
}
|
|
|
|
print('UrlMatcherService: 매칭 실패 - $lowerName');
|
|
return null;
|
|
} catch (e) {
|
|
print('UrlMatcherService: suggestUrl 에러 - $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// URL이 알려진 서비스 URL인지 확인
|
|
Future<bool> isKnownServiceUrl(String url) async {
|
|
final serviceInfo = await findServiceByUrl(url);
|
|
return serviceInfo != null;
|
|
}
|
|
|
|
/// 입력된 서비스 이름이나 문자열에서 매칭되는 URL을 찾아 반환
|
|
String? findMatchingUrl(String text, {bool usePartialMatch = true}) {
|
|
// 입력 텍스트가 비어있거나 null인 경우
|
|
if (text.isEmpty) {
|
|
return null;
|
|
}
|
|
|
|
// 소문자로 변환하여 처리
|
|
final String lowerText = text.toLowerCase().trim();
|
|
|
|
// 정확히 일치하는 경우
|
|
if (LegacyServiceData.allServices.containsKey(lowerText)) {
|
|
return LegacyServiceData.allServices[lowerText];
|
|
}
|
|
|
|
// 부분 일치 검색이 활성화된 경우
|
|
if (usePartialMatch) {
|
|
// 가장 긴 부분 매칭 찾기
|
|
String? bestMatch;
|
|
int maxLength = 0;
|
|
|
|
for (var entry in LegacyServiceData.allServices.entries) {
|
|
final String key = entry.key;
|
|
|
|
// 입력된 텍스트에 서비스 키워드가 포함되어 있거나, 서비스 키워드에 입력된 텍스트가 포함된 경우
|
|
if (lowerText.contains(key) || key.contains(lowerText)) {
|
|
// 더 긴 매칭을 우선시
|
|
if (key.length > maxLength) {
|
|
maxLength = key.length;
|
|
bestMatch = entry.value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bestMatch;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|