Skip to content

Commit

Permalink
Add QuestionScanTask to check for answer invalidations
Browse files Browse the repository at this point in the history
  • Loading branch information
Zomis committed Dec 15, 2019
1 parent fc611f9 commit 2742e26
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 20 deletions.
@@ -1,7 +1,15 @@
package net.zomis.duga.tasks

import com.fasterxml.jackson.databind.JsonNode
import net.zomis.duga.utils.StackExchangeAPI

class CommentScanTask {

// Requires learning
// Requires learning
fun fetchComments(site: String, fromDate: Long): JsonNode {
val filter = "!Fcb8.PVyNbcSSIFtmbqhHwtwVw"
return StackExchangeAPI().apiCall("comments?page=1&pagesize=100&fromdate=" + fromDate +
"&order=desc&sort=creation", site, filter)
}

}
107 changes: 107 additions & 0 deletions Duga/duga-aws/src/main/kotlin/net/zomis/duga/tasks/QuestionScanTask.kt
@@ -0,0 +1,107 @@
package net.zomis.duga.tasks

import com.fasterxml.jackson.databind.JsonNode
import net.zomis.duga.aws.DugaMessage
import net.zomis.duga.utils.StackExchangeAPI
import org.apache.commons.text.StringEscapeUtils
import org.slf4j.LoggerFactory
import java.time.Instant

class QuestionScanTask(val room: String, val site: String) : DugaTask {
private val FILTER = "!DEQ-Ts0KBm6n14zYUs8UZUsw.yj0rZkhsEKF2rI4kBp*yOHv4z4"
private val LATEST_QUESTIONS = "questions?order=desc&sort=activity"
private val logger = LoggerFactory.getLogger(javaClass)
private val stackAPI = StackExchangeAPI()

override fun perform(): List<DugaMessage> {
val activeQuestions = stackAPI.apiCall(LATEST_QUESTIONS, site, FILTER)
val lastCheck = Instant.now().minusSeconds(60 * 5)
return this.perform(activeQuestions, lastCheck).map {
DugaMessage(room, it)
}
}

private fun perform(result: JsonNode, lastCheck: Instant): List<String> {
val results = mutableListOf<String>()
val questions = result["items"]
questions.forEach { node ->
val edited = node["last_edit_date"].asLong()
val questionLink = node["link"].asText()
val op = formatDisplayName(node["owner"]["display_name"].asText())
val questionId = node["question_id"].asText()
if (edited >= lastCheck.epochSecond && node["answer_count"].asInt() > 0) {
logger.info("$questionId has been edited")
val edits = stackAPI.apiCall(editCall(questionId), "codereview", "!9YdnS7lAD")
logger.info("Edits fetched for $questionId: ${edits["items"].size()}. quota remaining ${edits["quota_remaining"]}")
val possibleInvalidations = codeChanges(edits, lastCheck)
if (!possibleInvalidations.isEmpty()) {
val link = questionLink.replace("/questions/.*", "/posts/$questionId/revisions")
val editor = possibleInvalidations.joinToString(", ") {invalidation ->
formatDisplayName(invalidation["user"]["display_name"].asText())
}

results.add("*possible answer invalidation by $editor on question by $op:* $link")
}
}
}
return results
}

private fun formatDisplayName(displayName: String): String {
return StringEscapeUtils.unescapeHtml4(displayName)
}

private fun codeChanges(edits: JsonNode, lastCheck: Instant): List<JsonNode> {
val result = mutableListOf<JsonNode>()
edits["items"].forEach {
if (!it.has("last_body")) {
return@forEach
}
if (it["creation_date"].asLong() < lastCheck.epochSecond) {
return@forEach
}
if (it["is_rollback"].asBoolean()) {
return@forEach
}
val code = stripNonCode(it["body"].asText())
val codeBefore = stripNonCode(it["last_body"].asText())
if (code != codeBefore) {
result.add(it)
}
}
return result
}

private fun editCall(id: String): String {
return "posts/$id/revisions"
}

private fun stripNonCode(originalPost: String): String {
val codeStart = "<code>"
val codeEnd = "</code>"

var post = originalPost
post = post.replace("[\\t ]", "")
var keepCount = 0
var index = post.indexOf(codeStart)
while (index >= 0) {
val endIndex = post.indexOf(codeEnd)
if (endIndex < 0) {
throw IllegalStateException()
}
val before = post.substring(0, keepCount)
val code = post.substring(index + codeStart.length, endIndex)
val after = post.substring(endIndex + codeEnd.length)
if (code.contains("\\n") || code.contains('\n')) {
post = before + code + after
keepCount += code.length
} else {
post = before + after
}
index = post.indexOf(codeStart)
}
return post.substring(0, keepCount)
}


}
Expand Up @@ -7,7 +7,7 @@ import java.io.IOException

class RatingDiffTask(private val room: String, private val site: String, private val users: List<String>) : DugaTask {

val stackApi = StackExchangeAPI()
private val stackApi = StackExchangeAPI()

override fun perform(): List<DugaMessage> {
try {
Expand Down Expand Up @@ -36,11 +36,7 @@ class RatingDiffTask(private val room: String, private val site: String, private
}
}

fun chatName(displayName: String): String {
return clearName(displayName).replace(" ", "");
}

fun clearName(dispName: String): String {
private fun clearName(dispName: String): String {
var displayName = dispName
while (displayName.contains("&#")) {
var replacement = displayName.substring(displayName.indexOf("&#") + 2)
Expand All @@ -49,13 +45,13 @@ class RatingDiffTask(private val room: String, private val site: String, private
val ch = Integer.parseInt(replacement)
displayName = displayName.replaceFirst("&#\\d+;", ch.toChar().toString())
} catch (ex: RuntimeException) {
displayName = displayName.replaceFirst("&#", "");
displayName = displayName.replaceFirst("&#", "")
}
}
return displayName
}

fun diffStr(str: StringBuilder, max: JsonNode, min: JsonNode, string: String, function: (JsonNode) -> Int) {
private fun diffStr(str: StringBuilder, max: JsonNode, min: JsonNode, string: String, function: (JsonNode) -> Int) {
str.append(string)
str.append(": ")
val maxValue = function(max)
Expand Down
Expand Up @@ -13,14 +13,15 @@ class TaskLambda : RequestHandler<Map<String, Any>, Map<String, Any>> {
val json = mapper.readTree(mapper.writeValueAsString(input))

val type = json["type"].asText()
val room = json["room"].asText()
val task: DugaTask? = when (type) {
"mess" -> MessageTask(json["room"]!!.asText(), json["message"]!!.asText())
"questionScan" -> null
"ratingdiff" -> RatingDiffTask(json["room"]!!.asText(),
"mess" -> MessageTask(room, json["message"]!!.asText())
"questionScan" -> QuestionScanTask(room, json["site"]!!.asText())
"ratingdiff" -> RatingDiffTask(room,
json["site"]!!.asText(),
json["users"]!!.map { it.asText() }
)
"unanswered" -> UnansweredTask(json["room"]!!.asText(),
"unanswered" -> UnansweredTask(room,
json["site"]!!.asText(),
json["message"]!!.asText()
)
Expand Down
Expand Up @@ -8,13 +8,7 @@ import java.util.zip.GZIPInputStream

class StackExchangeAPI {

val stackAPI = System.getenv("STACKEXCHANGE_API")

fun fetchComments(site: String, fromDate: Long): JsonNode {
val filter = "!Fcb8.PVyNbcSSIFtmbqhHwtwVw"
return apiCall("comments?page=1&pagesize=100&fromdate=" + fromDate +
"&order=desc&sort=creation", site, filter)
}
private val stackAPI = System.getenv("STACKEXCHANGE_API")

private fun buildURL(apiCall: String, site: String, filter: String, apiKey: String): URL {
var call = apiCall
Expand Down

0 comments on commit 2742e26

Please sign in to comment.