外に出ない自分を罵倒してくれるアプリを作った

この記事は[Qiita クソアプリ Advent Calendar 2023](https://qiita.com/advent-calendar/2023/kuso-app) シリーズ2　19日目の記事です。

## 初めに

フルリモートで働いているエンジニアの皆さんは、ほとんどが家で働いていることが多くて、
外に出ないことがあるのではないでしょうか？

自分も例に漏れずに、ずっとデスクの前にいて、全く歩かない日があります。


![歩数.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393442/9faf8d89-7419-ab4f-2e9a-641e79913216.jpeg)
**圧倒的歩数、！**

これではダメだ、外に出ないと、！
だけど外に出なくても叱ってくれる人もいない、外に出ない日は罵倒されたい、いや、罵倒してほしい
だけど時々褒めて欲しい

ということで、
その日に歩いた歩数をもとに罵倒してくれつつ(時々褒める)明日の運勢を占ってくれるアプリを作りました

[ソースコードはこちら](https://github.com/Take111/HosuuDeUranai)

## デモ

<img width="460" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393442/3b9a104a-31b6-4ec7-8fe3-f2567ba6d507.gif">

15歩しか歩いていないので、圧倒的な悪口、、！ありがとうございます。

ちなみにたくさん歩くと、こんなふうに褒めてくれます。

<img width="460" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393442/73207bec-e536-8ae5-690b-074c6d21120a.jpeg">

## 中身

中身は単純で、HealthKitで当日の歩数を取得して、それをもとにOpen AIのAPIを叩いています。

```HealthKitUseCase.swift

final class HealthKitUseCaseImpl: HealthKitUseCase {

    ~~~省略~~~
    func fetchTodayStepCount(completion: @escaping (Result<Int, Error>) -> Void) {
        // 当日の今までの歩数を取得する
        let distanceType = HKObjectType.quantityType(forIdentifier: .stepCount)!
        let now = Date()
        let startOfDay = Calendar.current.startOfDay(for: now)
        let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate)
        let query = HKStatisticsQuery(quantityType: distanceType, quantitySamplePredicate: predicate) { query, statistics, error in
            guard let stepCount = statistics?.sumQuantity()?.doubleValue(for: HKUnit.count()) else { return }
            if let error = error {
                completion(.failure(error))
                return
            }
            completion(.success(Int(stepCount)))
        }
        healthStore.execute(query)
    }
}
```

歩数の取得

```StepCountRange.swift
enum StepCountRange {
    case veryHigh
    case high
    case middle
    case low
    case veryLow

    init(stepCount: Int) {
        switch stepCount {
        case 0...100:
            self = .veryLow
        case 101...1000:
            self = .low
        case 1001...3000:
            self = .middle
        case 3001...6000:
            self = .high
        default:
            self = .veryHigh
        }
    }

  var prompt: String {
        switch self {
        case .veryHigh:
            return "あなたの運命は大吉ですから始めて、最高にすごいので、人生最大級くらいの褒め言葉を送ってください。実際の名言を教えて欲しい。今日1日を幸せに終わりたい"
        case .high:
            return "あなたの運命は中吉ですから始めて、少し良いので、ちょっとだけ褒めてください。最後に人生の格言みたいなことを言ってください"
        case .middle:
            return "あなたの運命は小吉ですから始めて、まあまあなので、適当に人生の格言っぽいことを言ってください"
        case .low:
            return "あなたの運命は凶ですから始めて、あんまり良くないので、ちょっと罵倒してください。あと適当に人生の格言っぽいことを言ってください"
        case .veryLow:
            return "あなたの運命は大凶ですから始めて、話にならないくらい罵倒してパワハラ上司並みに詰めてください。"
        }
    }
}
```

取得した歩数をもとにレンジを設けて、それをもとにプロンプトを作成してます。

```ChatGPTRepository.swift
final class ChatGPTRepositoryImpl: ChatGPTRepository {

    ~~~省略~~~
    func fetchTalkStreaming(chatQuery: ChatQuery) -> AsyncThrowingStream<ChatStreamResult, Error> {
        openAI.chatsStream(query: chatQuery)
    }
}
```

徐々に表示された方が体験として良いので、Streamで流しています。
OpenAIのAPIは最初は自作してましたが、便利な[ライブラリ](https://github.com/MacPaw/OpenAI)があることに気がついて乗り換えました。


## 終わり

もはやどこに占い要素があるのか？という感じですが、これで健康的な生活が送れそうです。
開発を経て、悪口のボキャブラリーも増やすことができました。


### 参考

全体のコード

https://github.com/Take111/HosuuDeUranai

OpenAIのドキュメント

https://platform.openai.com/docs/overview

HealthKit

https://developer.apple.com/documentation/healthkit

終わり！