diff --git a/src/en/aurora/AndroidManifest.xml b/src/en/aurora/AndroidManifest.xml
new file mode 100644
index 000000000..b4571bfa8
--- /dev/null
+++ b/src/en/aurora/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/en/aurora/build.gradle b/src/en/aurora/build.gradle
new file mode 100644
index 000000000..5ea6c0be0
--- /dev/null
+++ b/src/en/aurora/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+ext {
+ extName = 'aurora'
+ pkgNameSuffix = 'en.aurora'
+ extClass = '.Aurora'
+ extVersionCode = 1
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/en/aurora/res/mipmap-hdpi/ic_launcher.png b/src/en/aurora/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..89b5d3721
Binary files /dev/null and b/src/en/aurora/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/en/aurora/res/mipmap-mdpi/ic_launcher.png b/src/en/aurora/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..a884c581b
Binary files /dev/null and b/src/en/aurora/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/en/aurora/res/mipmap-xhdpi/ic_launcher.png b/src/en/aurora/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..13a2f9f1d
Binary files /dev/null and b/src/en/aurora/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/en/aurora/res/mipmap-xxhdpi/ic_launcher.png b/src/en/aurora/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..cdcd8c95e
Binary files /dev/null and b/src/en/aurora/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/en/aurora/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/aurora/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..a8b169a90
Binary files /dev/null and b/src/en/aurora/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/en/aurora/res/web_hi_res_512.png b/src/en/aurora/res/web_hi_res_512.png
new file mode 100644
index 000000000..2396a93b6
Binary files /dev/null and b/src/en/aurora/res/web_hi_res_512.png differ
diff --git a/src/en/aurora/src/eu/kanade/tachiyomi/extension/en/aurora/Aurora.kt b/src/en/aurora/src/eu/kanade/tachiyomi/extension/en/aurora/Aurora.kt
new file mode 100644
index 000000000..610b54cbe
--- /dev/null
+++ b/src/en/aurora/src/eu/kanade/tachiyomi/extension/en/aurora/Aurora.kt
@@ -0,0 +1,205 @@
+package eu.kanade.tachiyomi.extension.en.aurora
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.MangasPage
+import eu.kanade.tachiyomi.source.model.Page
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SManga
+import eu.kanade.tachiyomi.source.online.HttpSource
+import eu.kanade.tachiyomi.util.asJsoup
+import okhttp3.Request
+import okhttp3.Response
+import rx.Observable
+import java.lang.Exception
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+/**
+ * @author THE_ORONCO
+ */
+
+class Aurora : HttpSource() {
+
+ override val name = "Aurora"
+ override val baseUrl = "https://comicaurora.com"
+ override val lang = "en"
+ override val supportsLatest = false
+ private val authorName = "OSP-Red"
+ private val auroraGenre = "fantasy"
+ private val dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US)
+
+ override fun chapterListRequest(manga: SManga): Request = throw Exception("Not used")
+
+ override fun chapterListParse(response: Response): List = throw Exception("Not used")
+
+ override fun fetchChapterList(manga: SManga): Observable> {
+ return Observable.just(fetchChapterListTR(baseUrl + manga.url, mutableListOf()))
+ }
+
+ private tailrec fun fetchChapterListTR(
+ currentUrl: String,
+ foundChapters: MutableList
+ ): MutableList {
+ val currentPage = client.newCall(GET(currentUrl, headers)).execute().asJsoup()
+
+ val pagesAsChapters = currentPage.select(".post-content")
+ .map { postContent ->
+ val chapterUrl = postContent.select("a.webcomic-link").attr("href")
+ val title = postContent.select(".post-title a").text()
+ val chapterNr = title.substringAfter('.').toFloat()
+ val dateString = postContent.select(".post-date").text()
+ val date = dateFormat.parse(dateString)?.time ?: 0L
+
+ SChapter.create().apply {
+ setUrlWithoutDomain(chapterUrl)
+ name = title
+ chapter_number = chapterNr
+ date_upload = date
+ }
+ }
+
+ foundChapters.addAll(pagesAsChapters)
+
+ // get a potential next page of the chapter overview
+ val nextPageNavUrl = currentPage.selectFirst(".paginav-next a")?.attr("href")
+ // check if a next page actually exits and if not exit
+ return if (nextPageNavUrl == null) {
+ foundChapters
+ } else {
+ fetchChapterListTR(nextPageNavUrl, foundChapters)
+ }
+ }
+
+ override fun fetchImageUrl(page: Page): Observable {
+ return Observable.just(page.imageUrl)
+ }
+
+ override fun imageUrlParse(response: Response): String = throw Exception("Not used")
+
+ override fun latestUpdatesParse(response: Response): MangasPage = throw Exception("Not used")
+
+ override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used")
+
+ override fun fetchMangaDetails(manga: SManga): Observable {
+ val chapterNr = manga.title.substringAfter(' ').toFloatOrNull() ?: 0f
+
+ val updatedManga = SManga.create().apply {
+ setUrlWithoutDomain(manga.url)
+ title = manga.title
+ artist = authorName
+ author = authorName
+ description = auroraDescription
+ genre = auroraGenre
+ status = getChapterStatusForChapter(chapterNr)
+ thumbnail_url = manga.thumbnail_url
+ }
+ return Observable.just(updatedManga)
+ }
+
+ /**
+ * @param chapter chapter the status should be fetched for
+ * @return the status of the chapter (as Enum value of SManga because chapters are mangas)
+ */
+ private fun getChapterStatusForChapter(chapter: Float): Int {
+ val newestPage = client.newCall(GET(baseUrl)).execute().asJsoup()
+ val postTitle = newestPage.selectFirst(".post-title").text()
+ // title is ".."
+ val chapterOfNewestPage = postTitle.split(".")[1].toFloat()
+ return if (chapter >= chapterOfNewestPage) SManga.UNKNOWN else SManga.COMPLETED
+ }
+
+ private val auroraDescription = """
+ Aurora is a fantasy webcomic (updates M/W/F) written and illustrated by Red, better known for her work on the YouTube channel “Overly Sarcastic Productions.” It’s been in the works for over a decade, and she’s finally decided to stop putting it off.
+
+ If you’d like to discuss the comic, it now has a subreddit, as well as a dedicated twitter and a tumblr where you can ask questions. There’s also a dedicated room on the channel discord for conversations about it!
+
+ Find Red’s general ramblings on Twitter, alongside her cohost Blue, at OSPYouTube.
+ """.trimIndent()
+
+ override fun mangaDetailsParse(response: Response): SManga = throw Exception("Not used")
+
+ override fun fetchPageList(chapter: SChapter): Observable> {
+ val singlePageChapterDoc = client.newCall(
+ GET(baseUrl + chapter.url, headers)
+ ).execute().asJsoup()
+ val imageUrl = singlePageChapterDoc.selectFirst(
+ ".webcomic-media .webcomic-link .attachment-full"
+ ).attr("src")
+ val singlePageChapter = Page(0, "", imageUrl)
+
+ return Observable.just(listOf(singlePageChapter))
+ }
+
+ override fun pageListRequest(chapter: SChapter): Request = throw Exception("Not used")
+
+ override fun pageListParse(response: Response): List = throw Exception("Not used")
+
+ /**
+ * Because the comic is updated 1 page at a time the chapters are turned into different mangas
+ * so that the pages can be turned into different chapters which can be automatically updated by
+ * Tachiyomi.
+ *
+ * @return List of all Chapters as separate mangas
+ */
+ private fun fetchChaptersAsMangas(): List {
+ val descriptionText = auroraDescription
+
+ val chapterArchiveUrl = "$baseUrl/archive/"
+
+ val chapterOverviewDoc = client.newCall(GET(chapterArchiveUrl, headers)).execute().asJsoup()
+ val chapterBlockElements = chapterOverviewDoc.select(".blocks-gallery-item")
+ val mangasFromChapters = chapterBlockElements
+ .mapIndexed { chapterIndex, chapter ->
+ val chapterOverviewLink = chapter.selectFirst(".blocks-gallery-item__caption a")
+ val chapterOverviewUrl = chapterOverviewLink.attr("href")
+ val chapterTitle = "$name - ${chapterOverviewLink.text()}"
+ val chapterThumbnail = chapter.selectFirst("figure img").attr("src")
+
+ SManga.create().apply {
+ setUrlWithoutDomain(chapterOverviewUrl)
+ title = chapterTitle
+ author = authorName
+ artist = authorName
+ description = descriptionText
+ genre = auroraGenre
+ // this will mark every chapter except the last one as completed
+ status =
+ if (chapterIndex >= chapterBlockElements.size - 1) SManga.UNKNOWN
+ else SManga.COMPLETED
+ thumbnail_url = chapterThumbnail
+ }
+ }
+
+ return mangasFromChapters
+ }
+
+ /**
+ * Turn the list of chapters as mangas into the mangas page that can be returned for every
+ * request.
+ */
+ private fun generateAuroraMangasPage(): MangasPage {
+ return MangasPage(fetchChaptersAsMangas(), false)
+ }
+
+ override fun fetchPopularManga(page: Int): Observable {
+ return Observable.just(generateAuroraMangasPage())
+ }
+
+ override fun popularMangaParse(response: Response): MangasPage = throw Exception("Not used")
+
+ override fun popularMangaRequest(page: Int): Request = throw Exception("Not used")
+
+ override fun fetchSearchManga(
+ page: Int,
+ query: String,
+ filters: FilterList
+ ): Observable {
+ return Observable.just(generateAuroraMangasPage())
+ }
+
+ override fun searchMangaParse(response: Response): MangasPage = throw Exception("Not used")
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
+ throw Exception("Not used")
+}