diff --git a/.github/workflows/basic-checks.yml b/.github/workflows/basic-checks.yml index 4c487294c7..863a69e0da 100644 --- a/.github/workflows/basic-checks.yml +++ b/.github/workflows/basic-checks.yml @@ -14,7 +14,7 @@ jobs: uses: actions/setup-java@v1 with: java-version: ${{ env.JAVA_VERSION }} - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Check run: ./gradlew spotlessCheck @@ -26,7 +26,7 @@ jobs: uses: actions/setup-java@v1 with: java-version: ${{ env.JAVA_VERSION }} - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Check run: ./gradlew test @@ -38,7 +38,7 @@ jobs: uses: actions/setup-java@v1 with: java-version: ${{ env.JAVA_VERSION }} - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 2 - name: Check diff --git a/.github/workflows/code-analysis.yml b/.github/workflows/code-analysis.yml index 1122f4c27f..ba425f7d88 100644 --- a/.github/workflows/code-analysis.yml +++ b/.github/workflows/code-analysis.yml @@ -15,7 +15,7 @@ jobs: name: SonarCloud runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: # Shallow clones should be disabled for a better relevancy of analysis fetch-depth: 0 @@ -24,13 +24,13 @@ jobs: with: java-version: ${{ env.JAVA_VERSION }} - name: Cache SonarCloud packages - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Cache Gradle packages - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} @@ -64,7 +64,7 @@ jobs: with: java-version: ${{ env.JAVA_VERSION }} - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can check out the head. diff --git a/.github/workflows/discord-member.yml b/.github/workflows/discord-member.yml new file mode 100644 index 0000000000..8296e7467e --- /dev/null +++ b/.github/workflows/discord-member.yml @@ -0,0 +1,43 @@ +name: Comment When PR Merged +on: + pull_request: + types: + - closed + branches: + - develop + + +jobs: + if_merged: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v7 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const creator = context.payload.sender.login + const opts = github.rest.issues.listForRepo.endpoint.merge({ + ...context.issue, + creator, + state: 'all' + }) + const issues = await github.paginate(opts) + + for (const issue of issues) { + if (issue.number === context.issue.number) { + continue + } + + if (issue.pull_request) { + return + } + } + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `Congratulations on your first contribution! We're excited to have you on board. Your work will be included in the next release, so stay tuned! + Are you a Together-Java member on Discord? If so, please share your username here. If not, that’s perfectly fine! If you change your mind, here's the link: ${{vars.SERVER_INVITE}}. + Why do we need your username? We’d like to give you a shoutout to show our gratitude!` + }) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 9544b6e31a..a539fba120 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -21,7 +21,7 @@ jobs: shell: bash run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" id: extract_branch - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Build and Publish Docker Image diff --git a/.github/workflows/docker-verify.yaml b/.github/workflows/docker-verify.yaml index b1a5fc0294..c23528646d 100644 --- a/.github/workflows/docker-verify.yaml +++ b/.github/workflows/docker-verify.yaml @@ -14,7 +14,7 @@ jobs: uses: actions/setup-java@v1 with: java-version: ${{ env.JAVA_VERSION }} - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Docker Verify diff --git a/.github/workflows/releases.yaml b/.github/workflows/releases.yaml index 399f00ca4d..1f4dfa40fa 100644 --- a/.github/workflows/releases.yaml +++ b/.github/workflows/releases.yaml @@ -23,7 +23,7 @@ jobs: with: java-version: ${{ env.JAVA_VERSION }} - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Build shadow jar run: ./gradlew shadowJar diff --git a/application/build.gradle b/application/build.gradle index 08282a6890..7be2660eee 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'org.xerial:sqlite-jdbc:3.46.1.0' + classpath 'org.xerial:sqlite-jdbc:3.47.1.0' } } @@ -40,15 +40,15 @@ shadowJar { dependencies { implementation 'com.google.code.findbugs:jsr305:3.0.2' - implementation 'org.jetbrains:annotations:24.1.0' + implementation 'org.jetbrains:annotations:26.0.1' implementation project(':database') implementation project(':utils') implementation project(':formatter') - implementation 'net.dv8tion:JDA:5.1.0' + implementation 'net.dv8tion:JDA:5.2.1' - implementation 'org.apache.logging.log4j:log4j-core:2.23.0' + implementation 'org.apache.logging.log4j:log4j-core:2.24.3' runtimeOnly 'org.apache.logging.log4j:log4j-slf4j18-impl:2.18.0' implementation 'club.minnced:discord-webhooks:0.8.2' @@ -75,13 +75,13 @@ dependencies { implementation 'com.github.ben-manes.caffeine:caffeine:3.1.1' - implementation 'org.kohsuke:github-api:1.324' + implementation 'org.kohsuke:github-api:1.326' implementation 'org.apache.commons:commons-text:1.12.0' implementation 'com.apptasticsoftware:rssreader:3.8.1' - testImplementation 'org.mockito:mockito-core:5.12.0' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0' + testImplementation 'org.mockito:mockito-core:5.17.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.4' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.11.0' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.11.1' diff --git a/application/config.json.template b/application/config.json.template index 8f64df1a15..a32f8fd440 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -44,8 +44,7 @@ "hack", "steamcommunity", "freenitro", - "usd", - "earn", + "^earn", ".exe" ], "hostWhitelist": [ diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamBlocker.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamBlocker.java index f8b06f7eb6..0816e2fb5c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamBlocker.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamBlocker.java @@ -130,7 +130,11 @@ public void onMessageReceived(MessageReceivedEvent event) { return; } - boolean isSafe = !isBotTrapChannel.test(event.getChannel().asTextChannel()); + boolean isSafe = true; + if (event.getChannel() instanceof TextChannel textChannel + && isBotTrapChannel.test(textChannel)) { + isSafe = false; + } Message message = event.getMessage(); String content = message.getContentDisplay(); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamDetector.java b/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamDetector.java index 654cd87084..c4ebed3657 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamDetector.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/moderation/scam/ScamDetector.java @@ -67,7 +67,7 @@ private void analyzeToken(String token, AnalyseResults results) { results.containsSuspiciousKeyword = true; } - if (!results.containsDollarSign && token.contains("$")) { + if (!results.containsDollarSign && (token.contains("$") || "usd".equalsIgnoreCase(token))) { results.containsDollarSign = true; } @@ -114,7 +114,13 @@ private boolean containsSuspiciousKeyword(String token) { return config.getSuspiciousKeywords() .stream() .map(keyword -> keyword.toLowerCase(Locale.US)) - .anyMatch(preparedToken::contains); + .anyMatch(keyword -> { + // Simple regex-inspired syntax "^foo" + if (startsWith(keyword, '^')) { + return preparedToken.startsWith(keyword.substring(1)); + } + return preparedToken.contains(keyword); + }); } private boolean isHostSimilarToKeyword(String host, String keyword) { @@ -140,6 +146,10 @@ private boolean isHostSimilarToKeyword(String host, String keyword) { return false; } + private static boolean startsWith(CharSequence text, char prefixToTest) { + return !text.isEmpty() && text.charAt(0) == prefixToTest; + } + private static class AnalyseResults { private boolean pingsEveryone; private boolean containsSuspiciousKeyword; diff --git a/application/src/test/java/org/togetherjava/tjbot/features/moderation/scam/ScamDetectorTest.java b/application/src/test/java/org/togetherjava/tjbot/features/moderation/scam/ScamDetectorTest.java index f62dd31646..ba1ea14f08 100644 --- a/application/src/test/java/org/togetherjava/tjbot/features/moderation/scam/ScamDetectorTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/features/moderation/scam/ScamDetectorTest.java @@ -29,7 +29,7 @@ void setUp() { when(scamConfig.getSuspiciousKeywords()) .thenReturn(Set.of("nitro", "boob", "sexy", "sexi", "esex", "steam", "gift", "onlyfans", "bitcoin", "btc", "promo", "trader", "trading", "whatsapp", "crypto", "claim", - "teen", "adobe", "hack", "steamcommunity", "freenitro", "usd", "earn", ".exe")); + "teen", "adobe", "hack", "steamcommunity", "freenitro", "^earn", ".exe")); when(scamConfig.getHostWhitelist()).thenReturn(Set.of("discord.com", "discord.media", "discordapp.com", "discordapp.net", "discordstatus.com")); when(scamConfig.getHostBlacklist()).thenReturn(Set.of("bit.ly", "discord.gg", "teletype.in", @@ -54,6 +54,18 @@ void detectsRealScam(String scamMessage) { assertTrue(isScamResult); } + @ParameterizedTest + @MethodSource("provideRealFalsePositiveMessages") + @DisplayName("Can ignore real false positive messages") + void ignoresFalsePositives(String falsePositiveMessage) { + // GIVEN a real false positive message + // WHEN analyzing it + boolean isScamResult = scamDetector.isScam(falsePositiveMessage); + + // THEN does not flag it as scam + assertFalse(isScamResult); + } + @Test @DisplayName("Can detect messages that contain blacklisted websites as scam") void detectsBlacklistedWebsite() { @@ -227,4 +239,12 @@ private static List provideRealScamMessages() { "Urgently looking for mods & collab managers https://discord.gg/cryptohireo", "Check this - https://transfer.sh/get/ajmkh3l7tzop/Setup.exe"); } + + private static List provideRealFalsePositiveMessages() { + return List + .of(""" + https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/anonymous-types""", + """ + And according to quick google search. Median wage is about $23k usd"""); + } } diff --git a/build.gradle b/build.gradle index 3afbde256d..3546b7a0b0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id "com.diffplug.spotless" version "6.25.0" - id "org.sonarqube" version "5.1.0.4882" + id "org.sonarqube" version "6.0.1.5171" id "name.remal.sonarlint" version "4.2.2" } @@ -10,7 +10,7 @@ version '1.0-SNAPSHOT' ext { jooqVersion = '3.19.1' - jacksonVersion = '2.17.0' + jacksonVersion = '2.18.1' chatGPTVersion = '0.18.0' } diff --git a/database/build.gradle b/database/build.gradle index 9f996e19fb..9ec27d228d 100644 --- a/database/build.gradle +++ b/database/build.gradle @@ -2,12 +2,12 @@ plugins { id 'java' } -var sqliteVersion = "3.46.1.0" +var sqliteVersion = "3.47.1.0" dependencies { implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation "org.xerial:sqlite-jdbc:${sqliteVersion}" - implementation 'org.flywaydb:flyway-core:10.17.2' + implementation 'org.flywaydb:flyway-core:11.1.0' implementation "org.jooq:jooq:$jooqVersion" implementation project(':utils') diff --git a/formatter/build.gradle b/formatter/build.gradle index aeda44f388..41344106fc 100644 --- a/formatter/build.gradle +++ b/formatter/build.gradle @@ -6,7 +6,7 @@ dependencies { implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation project(':utils') - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.4' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.11.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.11.1' }