За вами следят! - Комментарии
За вами смотрят
Обзор кода имеет открытую систему
Машина, которая шпионит за вами каждый час каждый день
Я знаю, потому что я его построил. Я разработал машину для обнаружения предложений для публикации в обзоре кода, но он видит все Ужасные комментарии, связанные с обычными пользователями
Пользователи, как вы, Комментарии, которые Stack Exchange считает неуместными Они не будут действовать, поэтому я решил, что буду Но мне нужны были партнеры Регулярно с навыками вмешательства Любимый модераторами, мы работаем в чате
Вы можете легко найти нас Но по теме или вне темы, если ваш комментарий вверх Мы найдем вас ( личный интерес, вступивший в силу , адаптированный к комментариям по интересам)
Некоторое время пользователи Stack Overflow размещали комментарии, направляющие людей на пост в Code Review. Несколько раз в Stack Overflow Meta было отмечено, что пользователи должны быть осторожно при этом . Чтобы обучить пользователей Stack Overflow, на которых есть записи, которые здесь, а что нет, я решил включить эту функцию в мой SE-chatbot называется Duga .
Бот работает в среде Spring MVC. Он использует API-интерфейс стека , чтобы проверять комментарии «Переполнение стека» раз в две минуты. Он просматривает полученные комментарии и публикует их в втором мониторе , где завсегдатаи могут проверять переполнение стека вопрос, чтобы определить, принадлежит ли это кодовому обзору или нет. Он также отправляет некоторую информацию в специальном чате, когда есть сообщение, которое меня интересует, - когда в последнее время было слишком много комментариев или когда таинственная квота ставок сбрасывается. (Я не ожидаю, что это будет 100 комментариев очень часто. Пока это не произошло в течение двух минут)
Если вы хотите увидеть пример результата API, вы можете использовать этот ссылка
Репозиторий Github для всего бота можно найти здесь
ScheduledTasks.java (соответствующие части)
@Autowired
private ChatBot chatBot;
@Autowired
private StackExchangeAPIBean stackAPI;
private Instant nextFetch = Instant.now();
private long lastComment;
private long fromDate;
private int remainingQuota;
private final WebhookParameters params = WebhookParameters.toRoom("8595");
private final WebhookParameters debug = WebhookParameters.toRoom("20298");
@Scheduled(cron = "0 */2 * * * *") // second minute hour day day day
public void scanComments() {
if (!Instant.now().isAfter(nextFetch)) {
return;
}
try {
StackComments comments = stackAPI.fetchComments("stackoverflow", fromDate);
int currentQuota = comments.getQuotaRemaining();
if (currentQuota > remainingQuota && fromDate != 0) {
chatBot.postMessage(debug, Instant.now() + " Quota has been reset. Was " + remainingQuota + " is now " + currentQuota);
}
remainingQuota = currentQuota;
List<StackExchangeComment> items = comments.getItems();
if (items != null) {
if (items.size() >= 100) {
chatBot.postMessage(debug, Instant.now() + " Warning: Retrieved 100 comments. Might have missed some. This is unlikely to happen");
}
long previousLastComment = lastComment;
for (StackExchangeComment comment : items) {
if (comment.getCommentId() <= previousLastComment) {
continue;
}
lastComment = Math.max(comment.getCommentId(), lastComment);
fromDate = Math.max(comment.getCreationDate(), fromDate);
if (isInterestingComment(comment)) {
chatBot.postMessage(params, comment.getLink());
}
}
}
if (comments.getBackoff() != 0) {
nextFetch = Instant.now().plusSeconds(comments.getBackoff() + 10);
chatBot.postMessage(debug, Instant.now() + " Next fetch: " + nextFetch + " because of backoff " + comments.getBackoff());
}
} catch (Exception e) {
logger.error("Error retrieving comments", e);
chatBot.postMessage(debug, Instant.now() + " Exception in comment task " + e);
return;
}
}
private boolean isInterestingComment(StackExchangeComment comment) {
String commentText = comment.getBodyMarkdown().toLowerCase();
return commentText.contains("code review") || commentText.contains("codereview");
}
WebhookParameters.java
public class WebhookParameters {
private String roomId;
private Boolean post;
public String getRoomId() {
return roomId;
}
public void setRoomId(String roomId) {
this.roomId = roomId;
}
public void useDefaultRoom(String defaultRoomId) {
if (roomId == null) {
roomId = defaultRoomId;
}
}
public boolean getPost() {
return post == null ? true : post;
}
public void setPost(Boolean post) {
this.post = post;
}
public static WebhookParameters toRoom(String roomId) {
WebhookParameters params = new WebhookParameters();
params.setPost(true);
params.setRoomId(roomId);
return params;
}
}
StackExchangeAPIBean.java
public class StackExchangeAPIBean {
private final ObjectMapper mapper = new ObjectMapper();
@Autowired
private BotConfiguration config;
public StackExchangeAPIBean() {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public StackComments fetchComments(String site, long fromDate) throws JsonParseException, JsonMappingException, IOException {
final String filter = "!1zSk*x-OuqVk2k.(bS0NB";
final String apiKey = config.getStackAPIKey();
URL url = new URL("https://api.stackexchange.com/2.2/comments?page=1&pagesize=100&fromdate=" + fromDate +
"&order=desc&sort=creation&site=" + site + "&filter=" + filter + "&key=" + apiKey);
URLConnection connection = url.openConnection();
connection.setRequestProperty("Accept-Encoding", "identity");
return mapper.readValue(new GZIPInputStream(connection.getInputStream()), StackComments.class);
}
}
StackComments.java
public class StackComments {
@JsonProperty
private List<StackExchangeComment> items;
@JsonProperty("has_more")
private boolean hasMore;
@JsonProperty("quota_max")
private int quotaMax;
@JsonProperty("quota_remaining")
private int quotaRemaining;
@JsonProperty
private int backoff;
@JsonProperty("error_id")
private int errorId;
@JsonProperty("error_message")
private String errorMessage;
@JsonProperty("error_name")
private String errorName;
public int getBackoff() {
return backoff;
}
public int getErrorId() {
return errorId;
}
public String getErrorMessage() {
return errorMessage;
}
public String getErrorName() {
return errorName;
}
public List<StackExchangeComment> getItems() {
return items;
}
public int getQuotaMax() {
return quotaMax;
}
public int getQuotaRemaining() {
return quotaRemaining;
}
}
StackExchangeComment.java
public class StackExchangeComment {
@JsonProperty("post_id")
private long postId;
@JsonProperty("comment_id")
private long commentId;
@JsonProperty("creation_date")
private long creationDate;
@JsonProperty
private String body;
@JsonProperty
private String link;
@JsonProperty("body_markdown")
private String bodyMarkdown;
public String getBody() {
return body;
}
public String getBodyMarkdown() {
return bodyMarkdown;
}
public long getCommentId() {
return commentId;
}
public long getPostId() {
return postId;
}
public String getLink() {
return link;
}
public long getCreationDate() {
return creationDate;
}
}
Мне известно, что WebhookParameters
не является неизменным, это связано прежде всего с тем, что его можно использовать в качестве параметров запроса URL в Spring MVC.
Вопросы
- Есть ли способ упростить код?
- Я использую API-интерфейс Stack Exchange в хорошем смысле?
- Любые другие комментарии?
2 ответа
У меня есть одна в категории «любые другие комментарии»:)
Локализация?
Я знаю, это не проблема. Или это? Кажется, что локализация осталась позади! По иронии судьбы, именно по этой причине локализация приложения чаще всего - боль в шее.
Но тогда, насколько сложнее, действительно, написать это:
if (items.size() >= 100) { chatBot.postMessage(debug, Instant.now() + " Warning: Retrieved 100 comments. Might have missed some. This is unlikely to happen"); }
Вроде:
if (items.size() >= 100) {
chatBot.postMessage(debug, Instant.now() + Resources.Warn100CommentsReceived);
}
Я предполагаю java имеет что-то , например c # resources здесь, извинения, если я просто засунул ногу в рот. Он выглядит более чистым, хотя:)
«100» - это магическое число
Тем не менее, items.size() >= 100
не является «100 полученными комментариями» - сообщение не отражает то, что делает код, а это означает, что это когда-либо так мало возможно , что сообщение cake является ложью. И если вы когда-нибудь испортите этот предел, вы, вероятно, захотите узнать насколько.
Как получить извлечение переменной из items.size()
и объединить ее в сообщение вместо жестко закодированного 100
?
На основе последние 26 недель активности на стеке Переполнение , цифры будут следующими:
- 4 320 919 комментариев.
- Между комментариями 116 900 и 188 280 - 166 190 в среднем за неделю.
- В среднем за 52 недели 177,757 комментариев в неделю: месяцы впереди будут, вероятно, более занятыми, чем те, которые позади нас.
- Это 16,5 комментариев в минуту, 32.97 за две минуты.
Я согласен с тем, что 100
- разумное число для использования. Но ... «100» - магическое число ! В то время как вы получили 30 комментариев за две минуты, по крайней мере 4-5 новых пользователей присоединились к Stack Overflow (~ 23K новых пользователей в неделю) - при такой скорости 100
возможно, в конечном итоге, должно быть заменяется на большее число. Значение абсолютно произвольное (почему не 255?) И явно принадлежит как private static final
- это то, где я ожидал бы его найти, если не в настройках /файле приложения.
Как Матовая кружка уже , у вас есть некоторые магические числа в вашем коде.
private final WebhookParameters params = WebhookParameters.toRoom("8595"); private final WebhookParameters debug = WebhookParameters.toRoom("20298");
избавиться от них.
Вы не должны жестко закодировать api url. Вместо этого поместите его в файл конфигурации. Если api изменяется, значит, и URL. Если изменения не нарушают ваше приложение, возможно, URL.
Может быть, метод Url composeUrl()
будет хорошей идеей.
Жесткое кодирование значений, делающих комментарий интересным, также не должно выполняться.
ИМХО, вы немного разбираетесь в методе scanComments()
. Вы извлекаете комментарии из StackExchangeAPIBean
, обрабатываете оставшуюся квоту, обрабатываете комментарии и вычисляете следующую выборку по значению отсрочки. Это должно /могло быть извлечено для разделения методов.
Если исключение выбрано chatBot.postMessage
, это также произойдет внутри части catch
. Вы должны лучше заключить метод postMessage()
в try..catch и генерировать собственное исключение, которое затем не должно обрабатываться путем вызова postMessage()
снова.
Метод useDefaultRoom()
немного странный IMHO. Я бы переименовал его в setRoomIdIfNull()
, но я не вижу в этом никакого смысла (может быть, какой-то недостающий контекст?).