Skip to content
This repository has been archived by the owner on Dec 2, 2022. It is now read-only.

Commit

Permalink
monorepo
Browse files Browse the repository at this point in the history
  • Loading branch information
pvzig committed Jan 27, 2019
1 parent c2f3731 commit 239eb94
Show file tree
Hide file tree
Showing 89 changed files with 8,106 additions and 0 deletions.
1 change: 1 addition & 0 deletions Examples/Leaderboard/.swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.1.1
11 changes: 11 additions & 0 deletions Examples/Leaderboard/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import PackageDescription

let package = Package(
name: "Leaderbot",
targets: [
Target(name: "Leaderbot")
],
dependencies: [
.Package(url: "https://github.com/SlackKit/SlackKit", majorVersion: 4)
]
)
195 changes: 195 additions & 0 deletions Examples/Leaderboard/Sources/Leaderbot.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
//
// Leaderboard.swift
//
// Copyright © 2017 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation
import SlackKit

class Leaderbot {

struct Leaderboard {
let teamID: String
var scores = [String: Int]()

init(teamID: String) {
self.teamID = teamID
}
}

enum Command: String {
case leaderboard = "leaderboard"
}

enum Trigger: String {
case plusPlus = "++"
case minusMinus = "--"
}

let slackkit = SlackKit()

var leaderboards = [String: Leaderboard]()
let atSet = CharacterSet(charactersIn: "@")

init(apiToken: String) {
slackkit.addWebAPIAccessWithToken(apiToken)
slackkit.addRTMBotWithAPIToken(apiToken)
slackkit.notificationForEvent(.message) { [weak self] (event, client) in
self?.listen(client, message: event.message)
}
}

init(clientID: String, clientSecret: String) {
let oauthConfig = OAuthConfig(clientID: clientID, clientSecret: clientSecret)
slackkit.addServer(oauth: oauthConfig)
slackkit.notificationForEvent(.message) { [weak self] (event, client) in
self?.listen(client, message: event.message)
}
}

// MARK: Leaderboard Internal Logic
private func listen(_ client: Client?, message: Message?) {
guard let message = message, let text = message.text, let client = client else {
return
}
switch text {
case let text where text.lowercased().contains(Command.leaderboard.rawValue) && text.optionalContains(client.authenticatedUser?.id):
handleCommand(.leaderboard, channel: message.channel, client: client)
case let text where text.contains(Trigger.plusPlus.rawValue):
handleMessageWithTrigger(.plusPlus, message: message, client: client)
case let text where text.contains(Trigger.minusMinus.rawValue):
handleMessageWithTrigger(.minusMinus, message: message, client: client)
default:
break
}
}

private func handleMessageWithTrigger(_ trigger: Trigger, message: Message, client: Client) {
guard
let text = message.text,
let teamID = client.team?.id
else {
return
}
if leaderboards[teamID] == nil { leaderboards[teamID] = Leaderboard(teamID: teamID) }
//Nonusers
searchTextWithExpression("([a-z0-9_\\-\\.]+)[\\+\\-]{2}", text: text, trigger: trigger, teamID: teamID)
//Users
searchTextWithExpression("<@([A-Z0-9_\\-\\.]+)>[\\+\\-]{2}", text: text, trigger: trigger, teamID: teamID)
}

func searchTextWithExpression(_ expression: String, text: String, trigger: Trigger, teamID: String) {
let thingRegex = try? NSRegularExpression(pattern: expression, options: [])
let things = thingRegex?.matches(in: text, options: [], range: NSMakeRange(0, text.utf16.count)) ?? []
for match in things {
#if os(Linux)
let value = text.substring(with: text.range(from: match.range(at: 1))!)
#else
let value = text.substring(with: text.range(from: match.rangeAt(1))!)
#endif
if leaderboards[teamID]?.scores[value] == nil { leaderboards[teamID]?.scores[value] = 0 }
switch trigger {
case .plusPlus:
leaderboards[teamID]?.scores[value]?+=1
case .minusMinus:
leaderboards[teamID]?.scores[value]?-=1
}
}
}

private func handleCommand(_ command: Command, channel:String?, client: Client) {
switch command {
case .leaderboard:
if let id = channel {
slackkit.webAPI?.sendMessage(channel: id,
text: "Here's the leaderboard:",
linkNames: true,
attachments: [constructLeaderboardAttachment(client)],
success: nil,
failure: { (error) in
print("Leaderboard failed to post due to error:\(error)")
})
}
}
}

// MARK: Leaderboard Interface
private func constructLeaderboardAttachment(_ client: Client) -> Attachment? {
guard let teamID = client.team?.id, let leaderboard = leaderboards[teamID] else {
return nil
}
let top = AttachmentField(title: ":100:", value: swapIDsForNames(client, string: topItems(leaderboard)), short: true)
let bottom = AttachmentField(title: ":poop:", value: swapIDsForNames(client, string: bottomItems(leaderboard)), short: true)
return Attachment(fallback: "Leaderboard", title: "Leaderboard", colorHex: AttachmentColor.good.rawValue, text: "", fields: [top, bottom])
}

private func topItems(_ leaderboard: Leaderboard) -> String {
let sortedKeys = Array(leaderboard.scores.keys).sorted(by: {leaderboard.scores[$0]! > leaderboard.scores[$1]!}).filter({leaderboard.scores[$0]! > 0})
let sortedValues = Array(leaderboard.scores.values).sorted(by: {$0 > $1}).filter({$0 > 0})
return leaderboardString(sortedKeys, values: sortedValues)
}

private func bottomItems(_ leaderboard: Leaderboard) -> String {
let sortedKeys = Array(leaderboard.scores.keys).sorted(by: {leaderboard.scores[$0]! < leaderboard.scores[$1]!}).filter({leaderboard.scores[$0]! < 0})
let sortedValues = Array(leaderboard.scores.values).sorted(by: {$0 < $1}).filter({$0 < 0})
return leaderboardString(sortedKeys, values: sortedValues)
}

private func leaderboardString(_ keys: [String], values: [Int]) -> String {
var returnValue = ""
for i in 0..<values.count {
returnValue += keys[i] + " (" + "\(values[i])" + ")\n"
}
return returnValue
}

// MARK: - Utilities
private func swapIDsForNames(_ client: Client, string: String) -> String {
var returnString = string
for key in client.users.keys {
if let name = client.users[key]?.name {
returnString = returnString.replacingOccurrences(of: key, with: "@"+name, options: NSString.CompareOptions.literal, range: returnString.startIndex..<returnString.endIndex)
}
}
return returnString
}
}

extension String {
func optionalContains(_ string: String?) -> Bool {
guard let str = string else {
return false
}
return self.contains(str)
}
}

extension String {
func range(from nsRange: NSRange) -> Range<String.Index>? {
guard
let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex),
let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex),
let from = from16.samePosition(in: self),
let to = to16.samePosition(in: self)
else { return nil }
return from ..< to
}
}
8 changes: 8 additions & 0 deletions Examples/Leaderboard/Sources/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

//With OAuth
//let bot = Leaderbot(clientID: "CLIENT_ID", clientSecret: "CLIENT_SECRET")

//With API token
let bot = Leaderbot(apiToken: "xoxb-SLACK_BOT_TOKEN")
RunLoop.main.run()
32 changes: 32 additions & 0 deletions Examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Examples: SlackKit Examples
![Swift Version](https://img.shields.io/badge/Swift-3.1.1-orange.svg)
![Plaforms](https://img.shields.io/badge/Platforms-macOS,iOS,tvOS,Linux-lightgrey.svg)
![License MIT](https://img.shields.io/badge/License-MIT-lightgrey.svg)
[![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg)](https://github.com/Carthage/Carthage)
[![CocoaPods compatible](https://img.shields.io/badge/CocoaPods-compatible-brightgreen.svg)](https://cocoapods.org)

Example applications built with SlackKit.
## Leaderbot
A basic leaderboard scoring bot, in the spirit of [PlusPlus](https://plusplus.chat), built with [SlackKit](https://github.com/SlackKit/SlackKit).

To configure it, enter your bot’s API token in `main.swift` for the Leaderboard bot:

```swift
let bot = Leaderbot(apiToken: "xoxb-SLACK_BOT_TOKEN")
```

It adds a point for every `@user++` or `thing++`, subtracts a point for every `@user--` or `thing--`, and shows a leaderboard when asked `@botname leaderboard`.

## Robot-or-Not Bot
A bot that renders verdicts on if something is a [robot or not](https://www.theincomparable.com/robot/).

To configure it, enter your bot’s API token in `main.swift`:

```swift
let slackbot = RobotOrNotBot(token: "xoxb-SLACK_API_TOKEN")
```

It renders a robot or not verdict when asked `@bot THING`.

[Step By Step Tutorial](https://medium.com/@pvzig/building-slack-bots-in-swift-b99e243e444c)
1 change: 1 addition & 0 deletions Examples/robot-or-not-bot/.swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.1.1
8 changes: 8 additions & 0 deletions Examples/robot-or-not-bot/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import PackageDescription

let package = Package(
name: "robot-or-not-bot",
dependencies: [
.Package(url: "https://github.com/SlackKit/SlackKit", majorVersion: 4)
]
)
135 changes: 135 additions & 0 deletions Examples/robot-or-not-bot/Sources/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import Foundation
import SlackKit

class RobotOrNotBot {

let verdicts: [String:Bool] = [
"Mr. Roboto" : false,
"Service Kiosks": false,
"Darth Vader": false,
"K-9": true,
"Emotions": false,
"Self-Driving Cars": false,
"Telepresence Robots": false,
"Roomba": true,
"Assembly-Line Robot": false,
"ASIMO": false,
"KITT": false,
"USS Enterprise": false,
"Transformers": true,
"Jaegers": false,
"The Major": false,
"Siri": false,
"The Terminator": true,
"Commander Data": false,
"Marvin the Paranoid Android": true,
"Pinocchio": false,
"Droids": true,
"Hitchbot": false,
"Mars Rovers": false,
"Space Probes": false,
"Sasquatch": false,
"Toaster": false,
"Toaster Oven": false,
"Cylons": false,
"V'ger": true,
"Ilia Robot": false,
"The TARDIS": false,
"Johnny 5": true,
"Twiki": true,
"Dr. Theopolis": false,
"robots.txt": false,
"Lobot": false,
"Vicki": true,
"GlaDOS": false,
"Turrets": true,
"Wheatley": true,
"Herbie the Love Bug": false,
"Iron Man": false,
"Ultron": false,
"The Vision": false,
"Clockwork Droids": false,
"Podcasts": false,
"Cars": false,
"Swimming Pool Cleaners": false,
"Burritos": false,
"Prince Robot IV": false,
"Daleks": false,
"Cybermen": false,
"The Internet of Things": false,
"Nanobots": true,
"Two Intermeshed Gears": false,
"Crow T. Robot": true,
"Tom Servo": true,
"Thomas and Friends": false,
"Replicants": false,
"Chatbots": false,
"Agents": false,
"Lego Simulated Worm Toy": true,
"Ghosts": false,
"Exos": true,
"Rasputin": false,
"Tamagotchi": false,
"T-1000": true,
"The Tin Woodman": false,
"Mic N. The Robot": true,
"Robot Or Not Bot": false
]

let bot: SlackKit

init(token: String) {
bot = SlackKit()
bot.addRTMBotWithAPIToken(token)
bot.addWebAPIAccessWithToken(token)
bot.notificationForEvent(.message) { [weak self] (event, client) in
guard
let message = event.message,
let id = client?.authenticatedUser?.id,
message.text?.contains(id) == true
else {
return
}
self?.handleMessage(message)
}
}

init(clientID: String, clientSecret: String) {
bot = SlackKit()
let oauthConfig = OAuthConfig(clientID: clientID, clientSecret: clientSecret)
bot.addServer(oauth: oauthConfig)
bot.notificationForEvent(.message) { [weak self] (event, client) in
guard
let message = event.message,
let id = client?.authenticatedUser?.id,
message.text?.contains(id) == true
else {
return
}
self?.handleMessage(message)
}
}

// MARK: Bot logic
private func handleMessage(_ message: Message) {
if let text = message.text?.lowercased(), let channel = message.channel {
for (robot, verdict) in verdicts {
let lowerbot = robot.lowercased()
if text.contains(lowerbot) {
let reaction = verdict ? "robot_face" : "no_entry_sign"
bot.webAPI?.addReaction(name: reaction, channel: channel, timestamp: message.ts, success: nil, failure: nil)
return
}
}
// Not found
bot.webAPI?.addReaction(name: "question", channel: channel, timestamp: message.ts, success: nil, failure: nil)
return
}
}
}

// With API token
let slackbot = RobotOrNotBot(token: "xoxb-SLACK_API_TOKEN")
// With OAuth
// let slackbot = RobotOrNotBot(clientID: "CLIENT_ID", clientSecret: "CLIENT_SECRET")
RunLoop.main.run()
Loading

0 comments on commit 239eb94

Please sign in to comment.