Skip to content

Commit

Permalink
PurrBot.site api added
Browse files Browse the repository at this point in the history
  • Loading branch information
cranci1 committed Mar 16, 2024
1 parent f8caa3c commit e723bc6
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 52 deletions.
4 changes: 4 additions & 0 deletions AnimeGen.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
13C8011F2B94CAD900BFD198 /* HmtaiReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C8011E2B94CAD900BFD198 /* HmtaiReader.swift */; };
13CC95082B8BA40100B5705E /* ApiPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CC95072B8BA40100B5705E /* ApiPage.swift */; };
13CC950A2B8BA43600B5705E /* API.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13CC95092B8BA43600B5705E /* API.xcassets */; };
13E2DC842BA5D2CF00320E2F /* purr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13E2DC832BA5D2CF00320E2F /* purr.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -71,6 +72,7 @@
13C8011E2B94CAD900BFD198 /* HmtaiReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HmtaiReader.swift; sourceTree = "<group>"; };
13CC95072B8BA40100B5705E /* ApiPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiPage.swift; sourceTree = "<group>"; };
13CC95092B8BA43600B5705E /* API.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = API.xcassets; sourceTree = "<group>"; };
13E2DC832BA5D2CF00320E2F /* purr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = purr.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -181,6 +183,7 @@
138661242B8136DC0062AC91 /* nekosapi.swift */,
13BE98BF2B828B8000379AB7 /* nekosmoe.swift */,
139A2E092BA4D642003F2598 /* kyoko.swift */,
13E2DC832BA5D2CF00320E2F /* purr.swift */,
);
path = APIs;
sourceTree = "<group>";
Expand Down Expand Up @@ -280,6 +283,7 @@
files = (
13A325922B94D8A100F1C357 /* Secrets.swift in Sources */,
139A2E0A2BA4D642003F2598 /* kyoko.swift in Sources */,
13E2DC842BA5D2CF00320E2F /* purr.swift in Sources */,
13910EC72B80D5B9009BF17E /* waifu-im.swift in Sources */,
13877B192B82001800251A60 /* SettingsPage.swift in Sources */,
13BE98C02B828B8000379AB7 /* nekosmoe.swift in Sources */,
Expand Down
Binary file not shown.
103 changes: 103 additions & 0 deletions AnimeGen/APIs/purr.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//
// purr.swift
// AnimeGen
//
// Created by Francesco on 16/03/24.
//

import Foundation
import UIKit

extension ViewController {

func loadImageAndTagsFromPurr() {
startLoadingIndicator()

var categories: [String]
var endpointPrefix: String

if UserDefaults.standard.bool(forKey: "enableExplictiCont") {
categories = ["anal/gif", "blowjob/gif", "cum/gif", "fuck/gif", "neko/gif", "pussylick/gif", "solo/gif", "solo_male/gif", "threesome_fff/gif", "threesome_ffm/gif", "threesome_mmf/gif", "yuri/gif", "neko/img"]
endpointPrefix = "https://purrbot.site/api/img/nsfw/"
} else {
categories = ["angry/gif", "bite/gif", "blush/gif", "comfy/gif", "cry/gif", "cuddle/gif", "dance/gif", "eevee/gif", "fluff/gif", "holo/gif", "hug/gif", "kiss/gif", "lay/gif", "lick/gif", "neko/gif", "pat/gif", "poke/gif", "pout/gif", "slap/gif", "smile/gif", "tail/gif", "tickle/gif", "background/img", "eevee/img", "holo/img", "icon/img", "kitsune/img", "neko/img", "okami/img", "senko/img", "shiro/img"]
endpointPrefix = "https://purrbot.site/api/img/sfw/"
}

let randomCategory = categories.randomElement() ?? "neko/img"

var tag = randomCategory.components(separatedBy: "/").first ?? ""

let apiEndpoint = "\(endpointPrefix)\(randomCategory)"

guard let url = URL(string: apiEndpoint) else {
print("Invalid URL")
stopLoadingIndicator()
return
}

let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
DispatchQueue.main.async {
if let error = error {
print("Error: \(error)")
self.stopLoadingIndicator()
return
}

guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print("Invalid HTTP response")
self.stopLoadingIndicator()
return
}

if let data = data {
do {
if let jsonResponse = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let imageUrlString = jsonResponse["link"] as? String,
let imageUrl = URL(string: imageUrlString) {

if let imageData = try? Data(contentsOf: imageUrl) {
if imageUrlString.lowercased().hasSuffix(".gif") {
if let animatedImage = UIImage.animatedImage(with: UIImage.gifData(data: imageData) ?? [], duration: 1.0) {
self.imageView.image = animatedImage
self.animateImageChange(with: animatedImage)
} else {
print("Failed to create animated image from GIF data.")
}
} else {
if let newImage = UIImage(data: imageData) {
self.imageView.image = newImage
self.animateImageChange(with: newImage)
} else {
print("Failed to load image data.")
}
}

self.currentImageURL = imageUrlString

self.tagsLabel.isHidden = false

tag = tag.components(separatedBy: "/").first ?? ""
self.updateUIWithTags([tag])

self.stopLoadingIndicator()
} else {
print("Failed to load image data.")
self.stopLoadingIndicator()
}
} else {
print("Failed to parse JSON response or missing necessary data.")
self.stopLoadingIndicator()
}
}
} else {
print("No data received from server.")
self.stopLoadingIndicator()
}
}
}

task.resume()
}

}
21 changes: 21 additions & 0 deletions AnimeGen/Settings/API.xcassets/Purr.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "avatar.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions AnimeGen/Settings/ApiPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ struct ApiPage: View {
APIInfo(imageName: "Hmtai", apiName: "Hmtai", url: URL(string: "https://hmtai.hatsunia.cfd/endpoints")!),
APIInfo(imageName: "nekosapi", apiName: "nekosapi.com", url: URL(string: "https://nekosapi.com")!),
APIInfo(imageName: "nekos.moe", apiName: "nekos.moe", url: URL(string: "https://nekos.moe")!),
APIInfo(imageName: "kyoko", apiName: "Kyoko", url: URL(string: "https://api.rei.my.id/docs/ANIME/WAIFU-Generator/")!)
APIInfo(imageName: "kyoko", apiName: "Kyoko", url: URL(string: "https://api.rei.my.id/docs/ANIME/WAIFU-Generator/")!),
APIInfo(imageName: "Purr", apiName: "Purr", url: URL(string: "https://purrbot.site/")!)
]

var body: some View {
Expand All @@ -44,9 +45,9 @@ struct ApiPage: View {
}
}
.padding(EdgeInsets(top: 10, leading: 10, bottom: 30, trailing: 10))
.navigationBarHidden(true)
}
}
.navigationBarTitle(Text("Change App Icon"), displayMode: .inline)
}

private func apiItem(index: Int) -> some View {
Expand Down
83 changes: 44 additions & 39 deletions AnimeGen/Settings/IconPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,80 +8,85 @@
import SwiftUI

class IconNames: ObservableObject {
var iconNames: [String?] = [nil]
@Published var iconNames: [String] = []
@Published var currentIndex = 0

init() {
getAlternateIcons()
if let currentIcon = UIApplication.shared.alternateIconName {
self.currentIndex = iconNames.firstIndex(of: currentIcon) ?? 0
if let currentIcon = UIApplication.shared.alternateIconName,
let index = iconNames.firstIndex(of: currentIcon) {
self.currentIndex = index
}
}

func getAlternateIcons() {
if let icons = Bundle.main.object(forInfoDictionaryKey: "CFBundleIcons") as? [String: Any],
let alternateIcons = icons["CFBundleAlternateIcons"] as? [String: Any] {

if let iconsDictionary = Bundle.main.object(forInfoDictionaryKey: "CFBundleIcons") as? [String: Any],
let alternateIcons = iconsDictionary["CFBundleAlternateIcons"] as? [String: Any] {
for (_, value) in alternateIcons {
guard let iconList = value as? Dictionary<String, Any> else { return }
guard let iconFiles = iconList["CFBundleIconFiles"] as? [String] else { return }

guard let icon = iconFiles.first else { return }

iconNames.append(icon)
if let iconList = value as? [String: Any],
let iconFiles = iconList["CFBundleIconFiles"] as? [String],
let icon = iconFiles.first {
iconNames.append(icon)
}
}
}
}
}

struct IconPage: View {
@EnvironmentObject var iconSettings: IconNames
@State private var showErrorAlert = false

var body: some View {
NavigationView {
List {
ForEach(0 ..< iconSettings.iconNames.count) { i in
Button(action: {
self.iconSettings.currentIndex = i
self.changeAppIcon()
}) {
HStack {
Image(uiImage: UIImage(named: self.iconSettings.iconNames[i] ?? "AppIcon") ?? UIImage())
.resizable()
.renderingMode(.original)
.frame(width: 80, height: 80)
.clipShape(RoundedRectangle(cornerRadius: 12))
.overlay(RoundedRectangle(cornerRadius: 12).stroke(Color.gray, lineWidth: 1))
List {
ForEach(iconSettings.iconNames.indices, id: \.self) { index in
Button(action: {
changeAppIcon(index: index)
}) {
HStack {
Image(uiImage: UIImage(named: iconSettings.iconNames[index]) ?? UIImage())
.resizable()
.renderingMode(.original)
.frame(width: 80, height: 80)
.clipShape(RoundedRectangle(cornerRadius: 12))
.overlay(RoundedRectangle(cornerRadius: 12).stroke(Color.gray, lineWidth: 1))

Text(self.iconSettings.iconNames[i] ?? "Base")
.font(.headline)
.foregroundColor(.accentColor)
.lineLimit(1)
.padding(.leading, 8)
Text(iconSettings.iconNames[index])
.font(.headline)
.foregroundColor(.accentColor)
.lineLimit(1)
.padding(.leading, 8)

Spacer()
}
Spacer()
}
}
}
}
.alert(isPresented: $showErrorAlert) {
Alert(title: Text("Error"),
message: Text("Failed to change app icon. Please try again later."),
dismissButton: .default(Text("OK")))
}
.navigationBarTitle(Text("Change App Icon"), displayMode: .inline)
}

func changeAppIcon() {
let iconName = self.iconSettings.iconNames[self.iconSettings.currentIndex]
func changeAppIcon(index: Int) {
let iconName = iconSettings.iconNames[index]
UIApplication.shared.setAlternateIconName(iconName) { error in
if let error = error {
print("Error: \(error.localizedDescription)")
print("App Icon Change Error Code: \((error as NSError).code)")
} else {
print("Finished changing icon to \(iconName ?? "AppIcon")")
print("App Icon Change Error Code: \((error as NSError).code)")
showErrorAlert = true
} else {
print("Finished changing icon to \(iconName)")
iconSettings.currentIndex = index
}
}
}
}

struct IconPage_Previews: PreviewProvider {
static var previews: some View {
IconPage().preferredColorScheme(.dark).environmentObject(IconNames())
IconPage().environmentObject(IconNames())
}
}
7 changes: 6 additions & 1 deletion AnimeGen/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ class ViewController: UIViewController {
loadImageAndTagsFromNekosMoe()
case "kyoko":
loadImageAndTagsFromKyoko()
case "Purr":
loadImageAndTagsFromPurr()
default:
break
}
Expand Down Expand Up @@ -287,6 +289,9 @@ class ViewController: UIViewController {
case "kyoko":
lastImage = imageView.image
loadImageAndTagsFromKyoko()
case "Purr":
lastImage = imageView.image
loadImageAndTagsFromPurr()
default:
break
}
Expand All @@ -296,7 +301,7 @@ class ViewController: UIViewController {
@objc func apiButtonTapped() {
let alertController = UIAlertController(title: "Select API", message: nil, preferredStyle: .actionSheet)

let apiOptions = ["kyoko", "nekos.moe", "Nekos api", "Hmtai", "waifu.pics", "nekos.best", "waifu.im", "pic.re"]
let apiOptions = ["Purr", "kyoko", "nekos.moe", "Nekos api", "Hmtai", "waifu.pics", "nekos.best", "waifu.im", "pic.re"]
for option in apiOptions {
let action = UIAlertAction(title: option, style: .default) { _ in
self.apiButton.setTitle(option, for: .normal)
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ AnimeGen supports any iOS/iPadOS device running iOS 13+ (lower version wont be s
</tr>
<tr><td>
| APIs | Type | Working |
| ------------------- | ----- | :--------: |
| Pic.re | SFW | :white_check_mark: |
| Waifu.im | SFW/NSFW | :white_check_mark: |
| Nekos.best | SFW | :white_check_mark: |
| Waifu.pics | SFW/NSFW | :white_check_mark: |
| Hmtai | SFW/NSFW | :white_check_mark: |
| Nekos api | SFW/NSFW | :white_check_mark: |
| Nekos.mod | SFW/NSFW | :white_check_mark: |
| Kyoko | SFW/NSFW | :white_check_mark: |
| APIs | Type | Format | Working |
| ------------------- | ----- | ---- | :--------: |
| Pic.re | SFW | IMG | :white_check_mark: |
| Waifu.im | SFW/NSFW | IMG | :white_check_mark: |
| Nekos.best | SFW | IMG | :white_check_mark: |
| Waifu.pics | SFW/NSFW | IMG/GIF | :white_check_mark: |
| Hmtai | SFW/NSFW | IMG/GIF | :white_check_mark: |
| Nekos api | SFW/NSFW | IMG | :white_check_mark: |
| Nekos.moe | SFW/NSFW | IMG | :white_check_mark: |
| Kyoko | SFW/NSFW | IMG/GIF | :white_check_mark: |
| Purr | SFW/NSFW | IMG/GIF | :white_check_mark: |

</table>

Expand All @@ -38,6 +39,7 @@ AnimeGen supports any iOS/iPadOS device running iOS 13+ (lower version wont be s
- [Nekos api](https://nekosapi.com/docs)
- [Nekos.moe](https://docs.nekos.moe)
- [Kyoko](https://api.rei.my.id/docs/ANIME/WAIFU-Generator/)
- [Purr](https://purrbot.site/)

## Third Party Softwares

Expand Down

0 comments on commit e723bc6

Please sign in to comment.