aboutsummaryrefslogtreecommitdiff
path: root/HoloBar/CharacterFetcher.swift
blob: 44ad443818b3e139fdd044eb89ed66ebc37a6bd3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import Foundation
import SwiftSoup

class CharacterFetcher: ObservableObject {
  @Published var characters: [Character] = []

  init() {
    let today = Calendar.current.dateComponents([.month, .day], from: Date())

    if let month = today.month, let day = today.day {
      fetchCharacters(for: month, day: day)
    }
  }

  func fetchCharacters(for month: Int, day: Int) {
    let urlString =  // swiftlint:disable:next line_length
      "https://hololist.net/birthday/?birthday_month=\(String(format: "%02d", month))&birthday_day=\(String(format: "%02d", day))"
    guard let url = URL(string: urlString) else { return }

    let task = URLSession.shared.dataTask(with: url) { data, _, error in
      guard let data, error == nil,
        let html = String(data: data, encoding: .utf8)
      else { return }

      DispatchQueue.main.async {
        self.fetchAffiliations(for: self.parseHTML(html: html))
      }
    }

    task.resume()
  }

  private func parseHTML(html: String) -> [Character] {
    var fetchedCharacters: [Character] = []

    do {
      let document = try SwiftSoup.parse(html)
      let characterElements = try document.select("div.d-flex.mb-4.rounded")

      for element in characterElements {
        if let nameElement = try? element.select("a.line-truncate span").first(),
          let linkElement = try? element.select("a.line-truncate").first(),
          let profileHref = try? linkElement.attr("href"),
          let profileURL = URL(string: profileHref)
        {
          let name = try nameElement.text()

          fetchedCharacters
            .append(
              Character(
                name: name,
                profileURL: profileURL,
                avatarURL: profileURL,  // Avatar URLs are fetched in ``fetchAffiliations``
                rawName: name,
                affiliation: ""
              )
            )
        }
      }
    } catch {
      let blankURL = URL(string: "#")!

      fetchedCharacters
        .append(
          Character(
            name: "Error parsing HTML",
            profileURL: blankURL,
            avatarURL: blankURL,
            rawName: "",
            affiliation: ""
          )
        )
    }

    return fetchedCharacters
  }

  private func fetchAffiliations(for characters: [Character]) {
    let group = DispatchGroup()
    var updatedCharacters: [Character] = []

    for character in characters {
      group.enter()

      let task = URLSession.shared.dataTask(with: character.profileURL) { data, _, error in
        defer { group.leave() }

        guard let data,
          error == nil,
          let html = String(data: data, encoding: .utf8)
        else { return }

        do {
          let document = try SwiftSoup.parse(html)

          if let affiliationElement = try? document.select("#affiliation a").first(),
            let affiliation = try? affiliationElement.text()
          {
            updatedCharacters
              .append(
                Character(
                  name: "\(character.name) (\(affiliation))",
                  profileURL: character.profileURL,
                  avatarURL: (try? document.select("#left img").first()?.attr("data-src"))
                    .flatMap { URL(string: $0) } ?? character.avatarURL,
                  rawName: character.name,
                  affiliation: affiliation
                )
              )
          } else {
            updatedCharacters.append(character)
          }
        } catch {
          updatedCharacters.append(character)
        }
      }

      task.resume()
    }

    group.notify(queue: .main) {
      self.characters = updatedCharacters
    }
  }
}