Как-то работает

Добавил сообщение о смене статуса пр и смене решений ревьюверов
Не работает сохранение обновленных ПР
This commit is contained in:
Mark Struchkov 2020-02-04 15:36:07 +03:00
parent 44681c4a08
commit 71de4746fd
10 changed files with 186 additions and 77 deletions

View File

@ -64,7 +64,7 @@ public class AppConfig {
@Bean @Bean
public TaskScheduler taskScheduler() { public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(5); taskScheduler.setPoolSize(6);
return taskScheduler; return taskScheduler;
} }

View File

@ -0,0 +1,24 @@
package com.tsc.bitbucketbot.domain;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* TODO: Добавить описание класса.
*
* @author upagge [03.02.2020]
*/
@Data
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Pagination {
private Integer page;
private Integer size;
public static Pagination of(Integer page, Integer size) {
return new Pagination(page, size);
}
}

View File

@ -14,6 +14,7 @@ import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType; import javax.persistence.EnumType;
import javax.persistence.Enumerated; import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType; import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
@ -45,7 +46,7 @@ public class PullRequest {
@JoinColumn(name = "autor_login") @JoinColumn(name = "autor_login")
private User author; private User author;
@OneToMany(mappedBy = "pullRequestId", cascade = CascadeType.ALL) @OneToMany(mappedBy = "pullRequestId", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Reviewer> reviewers; private List<Reviewer> reviewers;
@Column(name = "url") @Column(name = "url")

View File

@ -3,7 +3,6 @@ package com.tsc.bitbucketbot.repository;
import com.tsc.bitbucketbot.domain.entity.PullRequest; import com.tsc.bitbucketbot.domain.entity.PullRequest;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Set; import java.util.Set;
/** /**
@ -13,4 +12,6 @@ import java.util.Set;
*/ */
public interface PullRequestsRepository extends JpaRepository<PullRequest, Long> { public interface PullRequestsRepository extends JpaRepository<PullRequest, Long> {
Set<PullRequest> findAllByIdIn(Set<Long> ids);
} }

View File

@ -26,7 +26,7 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
public class SchedulerNewUser { public class SchedulerNewUser {
public static final String URL = "http://192.168.236.164:7990/rest/api/1.0/admin/users"; private static final String URL = "http://192.168.236.164:7990/rest/api/1.0/admin/users";
private final UserService userService; private final UserService userService;
private final ConversionService conversionService; private final ConversionService conversionService;
private final BitbucketConfig bitbucketConfig; private final BitbucketConfig bitbucketConfig;

View File

@ -2,9 +2,9 @@ package com.tsc.bitbucketbot.scheduler;
import com.tsc.bitbucketbot.bitbucket.PullRequestJson; import com.tsc.bitbucketbot.bitbucket.PullRequestJson;
import com.tsc.bitbucketbot.bitbucket.UserDecisionJson; import com.tsc.bitbucketbot.bitbucket.UserDecisionJson;
import com.tsc.bitbucketbot.bitbucket.UserPullRequestStatus;
import com.tsc.bitbucketbot.bitbucket.sheet.PullRequestSheetJson; import com.tsc.bitbucketbot.bitbucket.sheet.PullRequestSheetJson;
import com.tsc.bitbucketbot.config.BitbucketConfig; import com.tsc.bitbucketbot.config.BitbucketConfig;
import com.tsc.bitbucketbot.domain.PullRequestStatus;
import com.tsc.bitbucketbot.domain.ReviewerStatus; import com.tsc.bitbucketbot.domain.ReviewerStatus;
import com.tsc.bitbucketbot.domain.entity.PullRequest; import com.tsc.bitbucketbot.domain.entity.PullRequest;
import com.tsc.bitbucketbot.domain.entity.Reviewer; import com.tsc.bitbucketbot.domain.entity.Reviewer;
@ -12,6 +12,7 @@ import com.tsc.bitbucketbot.domain.entity.User;
import com.tsc.bitbucketbot.service.PullRequestsService; import com.tsc.bitbucketbot.service.PullRequestsService;
import com.tsc.bitbucketbot.service.UserService; import com.tsc.bitbucketbot.service.UserService;
import com.tsc.bitbucketbot.service.Utils; import com.tsc.bitbucketbot.service.Utils;
import com.tsc.bitbucketbot.service.converter.PullRequestJsonConverter;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.sadtech.social.core.domain.BoxAnswer; import org.sadtech.social.core.domain.BoxAnswer;
@ -20,7 +21,6 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -37,7 +37,8 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
public class SchedulerPullRequest { public class SchedulerPullRequest {
private static final String URL = "http://192.168.236.164:7990/rest/api/1.0/dashboard/pull-requests?limit=50&state=OPEN"; private static final String URL_NEW_PR = "http://192.168.236.164:7990/rest/api/1.0/dashboard/pull-requests?limit=150&state=OPEN";
private static final String URL_OLD_PR = "http://192.168.236.164:7990/rest/api/1.0/dashboard/pull-requests?limit=150&closedSince=86400";
private final BitbucketConfig bitbucketConfig; private final BitbucketConfig bitbucketConfig;
private final PullRequestsService pullRequestsService; private final PullRequestsService pullRequestsService;
private final UserService userService; private final UserService userService;
@ -45,34 +46,34 @@ public class SchedulerPullRequest {
private final Sending sending; private final Sending sending;
@Scheduled(fixedRate = 15000) @Scheduled(fixedRate = 15000)
public void checkNewPullRequest() { public void checkOldPullRequest() {
final List<User> users = userService.getAllRegistered(); final List<User> users = userService.getAllRegistered();
for (User user : users) { for (User user : users) {
Optional<PullRequestSheetJson> sheetJson = Utils.urlToJson(URL, user.getToken(), PullRequestSheetJson.class); Optional<PullRequestSheetJson> sheetJson = Utils.urlToJson(URL_OLD_PR, user.getToken(), PullRequestSheetJson.class);
while (sheetJson.isPresent() && sheetJson.get().getValues() != null && !sheetJson.get().getValues().isEmpty()) { while (sheetJson.isPresent() && sheetJson.get().getValues() != null && !sheetJson.get().getValues().isEmpty()) {
final PullRequestSheetJson pullRequestBitbucketSheet = sheetJson.get(); final PullRequestSheetJson pullRequestBitbucketSheet = sheetJson.get();
final Set<Long> pullRequestBitbucketId = pullRequestBitbucketSheet.getValues().stream() Set<Long> existsId = pullRequestsService.existsById(
pullRequestBitbucketSheet.getValues().stream()
.map(PullRequestJson::getId) .map(PullRequestJson::getId)
.collect(Collectors.toSet());
Set<Long> existsId = pullRequestsService.existsAllById(pullRequestBitbucketId);
final Set<PullRequestJson> newPullRequestBitbucket = pullRequestBitbucketSheet.getValues().stream()
.filter(pullRequestJson -> !existsId.contains(pullRequestJson.getId()))
.collect(Collectors.toSet());
pullRequestsService.addAll(
newPullRequestBitbucket.stream()
.map(pullRequestJson -> conversionService.convert(pullRequestJson, PullRequest.class))
.collect(Collectors.toSet()) .collect(Collectors.toSet())
); );
final List<PullRequest> newPullRequests = new ArrayList<>(); final Map<Long, PullRequestJson> existsPullRequestBitbucket = pullRequestBitbucketSheet.getValues().stream()
for (PullRequestJson pullRequestJson : newPullRequestBitbucket) { .filter(pullRequestJson -> existsId.contains(pullRequestJson.getId()))
final List<Reviewer> reviewers = pullRequestJson.getReviewers().stream() .collect(Collectors.toMap(PullRequestJson::getId, pullRequestJson -> pullRequestJson));
.map(reviewer -> testConvert(pullRequestJson, reviewer)) final Set<PullRequest> pullRequests = pullRequestsService.getAllById(existsId);
.collect(Collectors.toList()); if (!existsPullRequestBitbucket.isEmpty() && !pullRequests.isEmpty()) {
pullRequestsService.addReviewer(pullRequestJson.getId(), reviewers).ifPresent(newPullRequests::add); processingUpdate(existsPullRequestBitbucket, pullRequests);
}
if (!existsPullRequestBitbucket.isEmpty()) {
pullRequestsService.updateAll(
existsPullRequestBitbucket.values().stream()
.map(pullRequestBitbucket -> conversionService.convert(pullRequestBitbucket, PullRequest.class))
.collect(Collectors.toList())
);
} }
sendNotification(newPullRequests);
if (pullRequestBitbucketSheet.getNextPageStart() != null) { if (pullRequestBitbucketSheet.getNextPageStart() != null) {
sheetJson = Utils.urlToJson(URL + pullRequestBitbucketSheet.getNextPageStart(), bitbucketConfig.getToken(), PullRequestSheetJson.class); sheetJson = Utils.urlToJson(URL_OLD_PR + pullRequestBitbucketSheet.getNextPageStart(), bitbucketConfig.getToken(), PullRequestSheetJson.class);
} else { } else {
break; break;
} }
@ -80,24 +81,81 @@ public class SchedulerPullRequest {
} }
} }
private Reviewer testConvert(PullRequestJson pullRequestJson, UserDecisionJson reviewer) { private void processingUpdate(Map<Long, PullRequestJson> existsPullRequestBitbucket, Set<PullRequest> pullRequests) {
final Reviewer newReviewer = new Reviewer(); for (PullRequest pullRequest : pullRequests) {
newReviewer.setPullRequestId(pullRequestJson.getId()); final PullRequestJson pullRequestBitbucket = existsPullRequestBitbucket.get(pullRequest.getId());
newReviewer.setUser(reviewer.getUser().getName()); final User author = pullRequest.getAuthor();
newReviewer.setStatus(convertStatusReviewer(reviewer.getStatus())); if (author.getTelegramId() != null) {
return newReviewer; sendStatusPR(pullRequest, pullRequestBitbucket);
sendReviewersPR(pullRequest, pullRequestBitbucket);
}
}
} }
private ReviewerStatus convertStatusReviewer(UserPullRequestStatus status) { private void sendReviewersPR(PullRequest pullRequest, PullRequestJson pullRequestBitbucket) {
switch (status) { final Map<String, Reviewer> oldReviewers = pullRequest.getReviewers().stream().collect(Collectors.toMap(Reviewer::getUser, reviewer -> reviewer));
case APPROVED: final List<UserDecisionJson> newReviewers = pullRequestBitbucket.getReviewers();
return ReviewerStatus.APPROVED; for (UserDecisionJson newReviewer : newReviewers) {
case NEEDS_WORK: if (oldReviewers.containsKey(newReviewer.getUser().getName())) {
return ReviewerStatus.UNAPPROVED; final Reviewer oldReviewer = oldReviewers.get(newReviewer.getUser().getName());
case UNAPPROVED: final ReviewerStatus oldStatus = oldReviewer.getStatus();
return ReviewerStatus.NEEDS_WORK; final ReviewerStatus newStatus = PullRequestJsonConverter.convertStatusReviewer(newReviewer.getStatus());
boolean flag = false;
StringBuilder stringBuilder = new StringBuilder("✏️ *Изменилось решение по вашему ПР*\n")
.append("[").append(pullRequest.getName()).append("](").append(pullRequest.getUrl()).append(")\n");
if (!oldStatus.equals(newStatus)) {
flag = true;
stringBuilder.append("\uD83D\uDC68\u200D\uD83D\uDCBB ").append(oldReviewer.getUser()).append(" ")
.append(oldStatus).append(" -> ").append(newStatus).append("\n");
}
if (flag) {
sending.send(pullRequest.getAuthor().getTelegramId(), BoxAnswer.of(stringBuilder.toString()));
}
}
}
}
private void sendStatusPR(PullRequest pullRequest, PullRequestJson pullRequestBitbucket) {
final PullRequestStatus oldStatus = pullRequest.getStatus();
final PullRequestStatus newStatus = PullRequestJsonConverter.convertPullRequestStatus(pullRequestBitbucket.getState());
if (!oldStatus.equals(newStatus)) {
StringBuilder stringBuilder = new StringBuilder("✏️ *Изменился статус вашего ПР*\n")
.append("[").append(pullRequest.getName()).append("](").append(pullRequest.getUrl()).append(")\n")
.append(oldStatus.name()).append(" -> ").append(newStatus.name())
.append("\n-- -- -- --\n")
.append("\uD83D\uDCCC: #pullRequest #change")
.append("\n\n");
sending.send(pullRequest.getAuthor().getTelegramId(), BoxAnswer.of(stringBuilder.toString()));
}
}
@Scheduled(fixedRate = 15000)
public void checkNewPullRequest() {
final List<User> users = userService.getAllRegistered();
for (User user : users) {
Optional<PullRequestSheetJson> sheetJson = Utils.urlToJson(URL_NEW_PR, user.getToken(), PullRequestSheetJson.class);
while (sheetJson.isPresent() && sheetJson.get().getValues() != null && !sheetJson.get().getValues().isEmpty()) {
final PullRequestSheetJson pullRequestBitbucketSheet = sheetJson.get();
final Set<Long> pullRequestBitbucketId = pullRequestBitbucketSheet.getValues().stream()
.map(PullRequestJson::getId)
.collect(Collectors.toSet());
Set<Long> existsId = pullRequestsService.existsAllIdById(pullRequestBitbucketId);
final Set<PullRequestJson> newPullRequestBitbucket = pullRequestBitbucketSheet.getValues().stream()
.filter(pullRequestJson -> !existsId.contains(pullRequestJson.getId()))
.collect(Collectors.toSet());
final List<PullRequest> newPullRequests = pullRequestsService.addAll(
newPullRequestBitbucket.stream()
.map(pullRequestJson -> conversionService.convert(pullRequestJson, PullRequest.class))
.collect(Collectors.toSet())
);
sendNotification(newPullRequests);
if (pullRequestBitbucketSheet.getNextPageStart() != null) {
sheetJson = Utils.urlToJson(URL_NEW_PR + pullRequestBitbucketSheet.getNextPageStart(), bitbucketConfig.getToken(), PullRequestSheetJson.class);
} else {
break;
}
}
} }
return null;
} }
private void sendNotification(@NonNull List<PullRequest> newPullRequests) { private void sendNotification(@NonNull List<PullRequest> newPullRequests) {
@ -118,9 +176,14 @@ public class SchedulerPullRequest {
final Long telegramId = user.getTelegramId(); final Long telegramId = user.getTelegramId();
if (telegramId != null) { if (telegramId != null) {
if (!map.containsKey(telegramId)) { if (!map.containsKey(telegramId)) {
map.put(telegramId, new StringBuilder("У вас есть новые ПР:\n\n")); map.put(telegramId, new StringBuilder());
} }
map.get(telegramId).append("*").append(pullRequest.getName()).append("*\n").append("Автор: ").append(pullRequest.getAuthor().getName()).append("\nСсылка: ").append(pullRequest.getUrl()).append("\n-- -- -- -- --\n\n"); map.get(telegramId).append("\uD83C\uDF89 *Новый Pull Request*\n")
.append("[").append(pullRequest.getName()).append("](").append(pullRequest.getUrl()).append(")\n")
.append("\uD83D\uDC68\u200D\uD83D\uDCBB: ").append(pullRequest.getAuthor().getName())
.append("\n-- -- -- -- --\n")
.append("\uD83D\uDCCC: ").append("#").append(pullRequest.getAuthor().getLogin()).append(" #pullRequest")
.append("\n\n");
} }
} }
); );

View File

@ -15,11 +15,14 @@ import java.util.Set;
*/ */
public interface PullRequestsService { public interface PullRequestsService {
Set<Long> existsById(@NonNull final Set<Long> idList); Set<Long> existsById(@NonNull final Set<Long> pullRequestId);
Set<Long> existsAllById(@NonNull Set<Long> pullRequestJsonId); Set<Long> existsAllIdById(@NonNull Set<Long> pullRequestId);
List<PullRequest> addAll(Set<PullRequest> pullRequests); Set<PullRequest> getAllById(@NonNull Set<Long> pullRequestJsonId);
List<PullRequest> addAll(@NonNull Set<PullRequest> pullRequests);
List<PullRequest> updateAll(@NonNull List<PullRequest> pullRequests);
Optional<PullRequest> addReviewer(@NonNull Long pullRequestJsonId, @NonNull List<Reviewer> reviewers);
} }

View File

@ -2,15 +2,22 @@ package com.tsc.bitbucketbot.service.converter;
import com.tsc.bitbucketbot.bitbucket.PullRequestJson; import com.tsc.bitbucketbot.bitbucket.PullRequestJson;
import com.tsc.bitbucketbot.bitbucket.PullRequestState; import com.tsc.bitbucketbot.bitbucket.PullRequestState;
import com.tsc.bitbucketbot.bitbucket.UserDecisionJson;
import com.tsc.bitbucketbot.bitbucket.UserJson; import com.tsc.bitbucketbot.bitbucket.UserJson;
import com.tsc.bitbucketbot.bitbucket.UserPullRequestStatus;
import com.tsc.bitbucketbot.domain.PullRequestStatus; import com.tsc.bitbucketbot.domain.PullRequestStatus;
import com.tsc.bitbucketbot.domain.ReviewerStatus;
import com.tsc.bitbucketbot.domain.entity.PullRequest; import com.tsc.bitbucketbot.domain.entity.PullRequest;
import com.tsc.bitbucketbot.domain.entity.Reviewer;
import com.tsc.bitbucketbot.domain.entity.User; import com.tsc.bitbucketbot.domain.entity.User;
import com.tsc.bitbucketbot.service.UserService; import com.tsc.bitbucketbot.service.UserService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
/** /**
* TODO: Добавить описание класса. * TODO: Добавить описание класса.
* *
@ -31,6 +38,7 @@ public class PullRequestJsonConverter implements Converter<PullRequestJson, Pull
.name(json.getTitle()) .name(json.getTitle())
.url(json.getLinks().getSelf().get(0).getHref()) .url(json.getLinks().getSelf().get(0).getHref())
.status(convertPullRequestStatus(json.getState())) .status(convertPullRequestStatus(json.getState()))
.reviewers(convertReviewers(json.getId(), json.getReviewers()))
.build(); .build();
} }
@ -38,7 +46,7 @@ public class PullRequestJsonConverter implements Converter<PullRequestJson, Pull
return userService.getByLogin(userJson.getName()).orElse(userJsonConverter.convert(userJson)); return userService.getByLogin(userJson.getName()).orElse(userJsonConverter.convert(userJson));
} }
private PullRequestStatus convertPullRequestStatus(PullRequestState state) { public static PullRequestStatus convertPullRequestStatus(PullRequestState state) {
switch (state) { switch (state) {
case OPEN: case OPEN:
return PullRequestStatus.OPEN; return PullRequestStatus.OPEN;
@ -50,18 +58,31 @@ public class PullRequestJsonConverter implements Converter<PullRequestJson, Pull
return null; return null;
} }
// private List<Reviewer> convertReviewers(PullRequest pullRequest, List<UserDecisionJson> jsonReviewers) { private List<Reviewer> convertReviewers(Long id, List<UserDecisionJson> jsonReviewers) {
// return jsonReviewers.stream() return jsonReviewers.stream()
// .map( .map(
// jsonReviewer -> { jsonReviewer -> {
// final Reviewer reviewer = new Reviewer(); final Reviewer reviewer = new Reviewer();
// reviewer.setReviewerKey(new ReviewerKey(convertUser(jsonReviewer.getUser()).getLogin())); reviewer.setPullRequestId(id);
// reviewer.setStatus(convertStatusReviewer(jsonReviewer.getStatus())); reviewer.setUser(jsonReviewer.getUser().getName());
// return reviewer; reviewer.setStatus(convertStatusReviewer(jsonReviewer.getStatus()));
// } return reviewer;
// ) }
// .collect(Collectors.toList()); )
// } .collect(Collectors.toList());
}
public static ReviewerStatus convertStatusReviewer(UserPullRequestStatus status) {
switch (status) {
case APPROVED:
return ReviewerStatus.APPROVED;
case NEEDS_WORK:
return ReviewerStatus.UNAPPROVED;
case UNAPPROVED:
return ReviewerStatus.NEEDS_WORK;
}
return null;
}

View File

@ -33,25 +33,23 @@ public class PullRequestsServiceImpl implements PullRequestsService {
} }
@Override @Override
public Set<Long> existsAllById(@NonNull Set<Long> pullRequestJsonId) { public Set<Long> existsAllIdById(@NonNull Set<Long> pullRequestJsonId) {
return pullRequestJsonId.stream().filter(pullRequestsRepository::existsById).collect(Collectors.toSet()); return pullRequestJsonId.stream().filter(pullRequestsRepository::existsById).collect(Collectors.toSet());
} }
@Override @Override
public List<PullRequest> addAll(Set<PullRequest> pullRequests) { public Set<PullRequest> getAllById(@NonNull Set<Long> pullRequestJsonId) {
return pullRequestsRepository.findAllByIdIn(pullRequestJsonId);
}
@Override
public List<PullRequest> addAll(@NonNull Set<PullRequest> pullRequests) {
return pullRequestsRepository.saveAll(pullRequests); return pullRequestsRepository.saveAll(pullRequests);
} }
@Override @Override
@Transactional public List<PullRequest> updateAll(@NonNull List<PullRequest> pullRequests) {
public Optional<PullRequest> addReviewer(@NonNull Long pullRequestId, @NonNull List<Reviewer> reviewers) { return pullRequestsRepository.saveAll(pullRequests);
final Optional<PullRequest> optPullRequest = pullRequestsRepository.findById(pullRequestId);
if (optPullRequest.isPresent()) {
final PullRequest pullRequest = optPullRequest.get();
pullRequest.setReviewers(reviewers);
return Optional.of(pullRequestsRepository.save(pullRequest));
}
return Optional.empty();
} }
} }

View File

@ -7,7 +7,7 @@ spring:
liquibase: liquibase:
change-log: classpath:liquibase/change-log.xml change-log: classpath:liquibase/change-log.xml
jpa: jpa:
show-sql: false show-sql: true
hibernate: hibernate:
ddl-auto: none ddl-auto: none
database-platform: org.hibernate.dialect.PostgreSQLDialect database-platform: org.hibernate.dialect.PostgreSQLDialect
@ -17,15 +17,13 @@ spring:
lob: lob:
non_contextual_creation: true non_contextual_creation: true
bitbucketbot: bitbucketbot:
panel:
password: mnbyNie832r
telegram: telegram:
bot-username: bitbucket_sadtech_bot bot-username: bitbucket_sadtech_bot
bot-token: 1096235968:AAHvIy_mlZJXiNc9aDQWtCuiksz9YGknoXE bot-token: 1096235968:AAHvIy_mlZJXiNc9aDQWtCuiksz9YGknoXE
admin-chatid: 3000811 admin-chatid: 3000811
proxy-config: proxy-config:
host: 54.39.16.26 host: 167.172.171.134
port: 62509 port: 1080
type: SOCKS5 type: SOCKS5
bitbucket: bitbucket:
token: Nzg5NjUyNDQwMzk2OlA+6naQz02+GxOG0Q9li/jnsn7E token: Nzg5NjUyNDQwMzk2OlA+6naQz02+GxOG0Q9li/jnsn7E