-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR adds one core dependency and a bunch of infrastrucutre to support login and login-based actions. Because HN doesn't have official API support for Login, Upvotes, Commenting, etc, the only way that clients can perform those actions is by requesting various web routes and scraping the HTML for data. Added `jsoup` as a dependency to make working with HTML a lot nicer, as it also allows you to query HTML documents using CSS selectors. Also created a `WebClient` which is reponsible for all things that require scraping the Hacker News website. Lastly I `CookieJar` to our OkHttp client so that it can store the "auth cookie" when we successfully login. We need this cooking on all subsequent web client requests in order to get access to actions.
- Loading branch information
Showing
17 changed files
with
694 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
android/app/src/main/java/com/emergetools/hackernews/data/HackerNewsWebClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package com.emergetools.hackernews.data | ||
|
||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.withContext | ||
import okhttp3.FormBody | ||
import okhttp3.OkHttpClient | ||
import okhttp3.Request | ||
import org.jsoup.Jsoup | ||
|
||
const val BASE_WEB_URL = "https://news.ycombinator.com/" | ||
private const val LOGIN_URL = BASE_WEB_URL + "login" | ||
private const val ITEM_URL = BASE_WEB_URL + "item" | ||
|
||
data class ItemPage( | ||
val id: Long, | ||
val upvoted: Boolean, | ||
val upvoteUrl: String | ||
) | ||
|
||
enum class LoginResponse { | ||
Success, | ||
Failed | ||
} | ||
|
||
class HackerNewsWebClient( | ||
private val httpClient: OkHttpClient, | ||
) { | ||
suspend fun login(username: String, password: String): LoginResponse { | ||
return withContext(Dispatchers.IO) { | ||
val response = httpClient.newCall( | ||
Request.Builder() | ||
.url(LOGIN_URL) | ||
.post( | ||
FormBody.Builder() | ||
.add("acct", username) | ||
.add("pw", password) | ||
.build() | ||
) | ||
.build() | ||
).execute() | ||
|
||
val document = Jsoup.parse(response.body?.string()!!) | ||
|
||
val body = document.body() | ||
val firstElement = body.firstChild() | ||
val loginFailed = firstElement?.toString()?.contains("Bad login") ?: false | ||
|
||
if (loginFailed) { | ||
LoginResponse.Failed | ||
} else { | ||
LoginResponse.Success | ||
} | ||
} | ||
} | ||
suspend fun getItemPage(itemId: Long): ItemPage { | ||
return withContext(Dispatchers.IO) { | ||
// request page | ||
val response = httpClient.newCall( | ||
Request | ||
.Builder() | ||
.url("$ITEM_URL?id=$itemId") | ||
.build() | ||
).execute() | ||
|
||
val document = Jsoup.parse(response.body?.string()!!) | ||
val upvoteElement = document.select("#up_$itemId") | ||
val upvoteHref = upvoteElement.attr("href") | ||
|
||
ItemPage( | ||
id = itemId, | ||
upvoted = upvoteElement.hasClass("nosee"), | ||
upvoteUrl = BASE_WEB_URL + upvoteHref | ||
) | ||
} | ||
} | ||
|
||
suspend fun upvoteItem(url: String): Boolean { | ||
return withContext(Dispatchers.IO) { | ||
val response = httpClient.newCall( | ||
Request.Builder() | ||
.url(url) | ||
.build() | ||
).execute() | ||
|
||
response.isSuccessful | ||
} | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
android/app/src/main/java/com/emergetools/hackernews/data/LocalCookieJar.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.emergetools.hackernews.data | ||
|
||
import android.util.Log | ||
import kotlinx.coroutines.flow.first | ||
import kotlinx.coroutines.runBlocking | ||
import okhttp3.Cookie | ||
import okhttp3.CookieJar | ||
import okhttp3.HttpUrl | ||
|
||
class LocalCookieJar(private val userStorage: UserStorage): CookieJar { | ||
|
||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) { | ||
Log.d("Cookie Jar", "Url: $url, cookie = ${cookies[0]}") | ||
cookies.firstOrNull { it.name == "user" }?.let { authCookie -> | ||
runBlocking { userStorage.saveCookie(authCookie.value) } | ||
} | ||
} | ||
|
||
override fun loadForRequest(url: HttpUrl): List<Cookie> { | ||
val authCookie = runBlocking { userStorage.getCookie().first() } | ||
Log.d("Cookie Jar", "Cookie: user=$authCookie" ) | ||
return if (authCookie != null) { | ||
val cookie = Cookie.Builder() | ||
.name("user") | ||
.value(authCookie) | ||
.domain("news.ycombinator.com") | ||
.build() | ||
listOf(cookie) | ||
} else { | ||
emptyList() | ||
} | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
android/app/src/main/java/com/emergetools/hackernews/data/UserStorage.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.emergetools.hackernews.data | ||
|
||
import android.content.Context | ||
import androidx.datastore.preferences.core.edit | ||
import androidx.datastore.preferences.core.stringPreferencesKey | ||
import com.emergetools.hackernews.dataStore | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.map | ||
|
||
class UserStorage(private val appContext: Context) { | ||
private val cookieKey = stringPreferencesKey("Cookie") | ||
|
||
suspend fun saveCookie(cookie: String) { | ||
appContext.dataStore.edit { store -> | ||
store[cookieKey] = cookie | ||
} | ||
} | ||
|
||
fun getCookie(): Flow<String?> { | ||
return appContext.dataStore.data.map { it[cookieKey] } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.