Skip to content

Commit d608cfc

Browse files
Re-wrote urlwatcher script using swift
Errors taking screenshots are now only reported after a given timespan (currently set fixed to 30 minutes in main.swift) The diff.png file will now only be overwritten if there really was a change and will no longer represent the diff between the last two screenshots! The NotifierBot /diff command should return the date of the diff.png file in the future.
1 parent 3668275 commit d608cfc

File tree

9 files changed

+679
-0
lines changed

9 files changed

+679
-0
lines changed

urlwatcher/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj
5+
xcuserdata/

urlwatcher/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1240"
4+
version = "1.3">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "urlwatcher"
18+
BuildableName = "urlwatcher"
19+
BlueprintName = "urlwatcher"
20+
ReferencedContainer = "container:">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
</BuildActionEntries>
24+
</BuildAction>
25+
<TestAction
26+
buildConfiguration = "Debug"
27+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
shouldUseLaunchSchemeArgsEnv = "YES">
30+
<Testables>
31+
</Testables>
32+
</TestAction>
33+
<LaunchAction
34+
buildConfiguration = "Debug"
35+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
36+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
37+
launchStyle = "0"
38+
useCustomWorkingDirectory = "YES"
39+
customWorkingDirectory = "/Users/jonasfrey/Documents/_Developing/Xcode Projects/Bots/NotifierBot/urlwatcher"
40+
ignoresPersistentStateOnLaunch = "NO"
41+
debugDocumentVersioning = "YES"
42+
debugServiceExtension = "internal"
43+
allowLocationSimulation = "YES">
44+
<BuildableProductRunnable
45+
runnableDebuggingMode = "0">
46+
<BuildableReference
47+
BuildableIdentifier = "primary"
48+
BlueprintIdentifier = "urlwatcher"
49+
BuildableName = "urlwatcher"
50+
BlueprintName = "urlwatcher"
51+
ReferencedContainer = "container:">
52+
</BuildableReference>
53+
</BuildableProductRunnable>
54+
</LaunchAction>
55+
<ProfileAction
56+
buildConfiguration = "Release"
57+
shouldUseLaunchSchemeArgsEnv = "YES"
58+
savedToolIdentifier = ""
59+
useCustomWorkingDirectory = "NO"
60+
debugDocumentVersioning = "YES">
61+
<BuildableProductRunnable
62+
runnableDebuggingMode = "0">
63+
<BuildableReference
64+
BuildableIdentifier = "primary"
65+
BlueprintIdentifier = "urlwatcher"
66+
BuildableName = "urlwatcher"
67+
BlueprintName = "urlwatcher"
68+
ReferencedContainer = "container:">
69+
</BuildableReference>
70+
</BuildableProductRunnable>
71+
</ProfileAction>
72+
<AnalyzeAction
73+
buildConfiguration = "Debug">
74+
</AnalyzeAction>
75+
<ArchiveAction
76+
buildConfiguration = "Release"
77+
revealArchiveInOrganizer = "YES">
78+
</ArchiveAction>
79+
</Scheme>

urlwatcher/Package.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// swift-tools-version:5.3
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "urlwatcher",
8+
platforms: [
9+
.macOS(.v10_15),
10+
],
11+
dependencies: [
12+
// Dependencies declare other packages that this package depends on.
13+
// .package(url: /* package url */, from: "1.0.0"),
14+
],
15+
targets: [
16+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
17+
// Targets can depend on other targets in this package, and on products in packages this package depends on.
18+
.target(
19+
name: "urlwatcher",
20+
dependencies: []),
21+
]
22+
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// BotPermission.swift
3+
//
4+
//
5+
// Created by Jonas Frey on 14.06.20.
6+
//
7+
8+
import Foundation
9+
10+
enum BotPermission: String, Comparable, CaseIterable {
11+
case user = "user"
12+
case mod = "mod"
13+
case admin = "admin"
14+
15+
static private let levels: [BotPermission: Int] = [
16+
.user: 0,
17+
.mod: 1,
18+
.admin: 2
19+
]
20+
21+
static func <(lhs: BotPermission, rhs: BotPermission) -> Bool {
22+
return levels[lhs]! < levels[rhs]!
23+
}
24+
25+
static func >(lhs: BotPermission, rhs: BotPermission) -> Bool {
26+
return levels[lhs]! > levels[rhs]!
27+
}
28+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Jonas Frey on 12.06.20.
6+
//
7+
8+
import Foundation
9+
10+
enum ConfigError: Error {
11+
case malformedLineSegments(String)
12+
case malformedIntegers(String)
13+
}
14+
15+
class ConfigParser {
16+
17+
static let shared = ConfigParser()
18+
19+
typealias Config = [URLEntry]
20+
21+
var permissions: [Int64: BotPermission] = parsePermissions()
22+
23+
private init() {}
24+
25+
/// Parses the file on disk into an internal structure
26+
static func getConfig() throws -> Config {
27+
let config = (try? String(contentsOfFile: urlListFile)) ?? ""
28+
var entries: [URLEntry] = []
29+
for line in config.components(separatedBy: .newlines) {
30+
if line.isEmpty {
31+
continue
32+
}
33+
let components = line.components(separatedBy: ",")
34+
// Name, x, y, width, height, url (url may contain comma)
35+
guard components.count >= 9 else {
36+
throw ConfigError.malformedLineSegments(line)
37+
}
38+
var args = components
39+
40+
func nextArg() -> String { args.removeFirst().trimmingCharacters(in: .whitespaces) }
41+
42+
let name = nextArg()
43+
let x = Int(nextArg())
44+
let y = Int(nextArg())
45+
let width = Int(nextArg())
46+
let height = Int(nextArg())
47+
let delay = Int(nextArg())
48+
let captureElement = nextArg()
49+
let clickElement = nextArg()
50+
let waitElement = nextArg()
51+
let chatID = Int64(nextArg())
52+
// URL is the rest
53+
let url = args.joined(separator: ",").trimmingCharacters(in: .whitespaces)
54+
55+
guard x != nil && y != nil && width != nil && height != nil && chatID != nil, delay != nil else {
56+
throw ConfigError.malformedIntegers(line)
57+
}
58+
59+
entries.append(URLEntry(
60+
name: name,
61+
url: url,
62+
area: Rectangle(x: x!, y: y!, width: width!, height: height!),
63+
chatID: chatID!,
64+
delay: delay!,
65+
captureElement: captureElement,
66+
clickElement: clickElement,
67+
waitElement: waitElement))
68+
}
69+
70+
return entries
71+
}
72+
73+
/// Parses the config back into a string and saves it to disk
74+
static func saveConfig(_ config: Config) throws {
75+
var configString = ""
76+
for l in config {
77+
configString += "\(l.name),\(l.area.x),\(l.area.y),\(l.area.width),\(l.area.height),\(l.delay),\(l.captureElement),\(l.clickElement),\(l.waitElement),\(l.chatID),\(l.url)\n"
78+
}
79+
// Remove the trailing line break
80+
configString.removeLast()
81+
try configString.write(toFile: urlListFile, atomically: true, encoding: .utf8)
82+
}
83+
84+
static func parsePermissions() -> [Int64: BotPermission] {
85+
guard let permissionsFile = try? String(contentsOfFile: permissionsFile) else {
86+
return [:]
87+
}
88+
var permissions: [Int64: BotPermission] = [:]
89+
for line in permissionsFile.components(separatedBy: .newlines) {
90+
if line.isEmpty { continue }
91+
let components = line.components(separatedBy: ":")
92+
guard components.count == 2 else {
93+
print("Error reading permissions: Malformed permission. Expected userID: permissionLevel.\n \(line)")
94+
print("Skipping this permission...")
95+
continue
96+
}
97+
let userID = Int64(components[0].trimmingCharacters(in: .whitespaces))
98+
let permissionLevel = BotPermission(rawValue: components[1].trimmingCharacters(in: .whitespaces))
99+
guard userID != nil && permissionLevel != nil else {
100+
print("Error reading permissions: Malformed permission. Expected userID: permissionLevel.\n \(line)")
101+
print("Skipping this permission...")
102+
continue
103+
}
104+
permissions[userID!] = permissionLevel!
105+
}
106+
return permissions
107+
}
108+
109+
func savePermissions() throws {
110+
let file = permissions.map { (userID: Int64, permission: BotPermission) in
111+
"\(userID): \(permission.rawValue)"
112+
}.joined(separator: "\n")
113+
try file.write(toFile: permissionsFile, atomically: true, encoding: .utf8)
114+
}
115+
116+
func permissionGroup(user: Int64) -> BotPermission {
117+
return permissions[user] ?? .user
118+
}
119+
120+
func setPermissionGroup(user: Int64, level: BotPermission) throws {
121+
permissions[user] = level
122+
try savePermissions()
123+
}
124+
125+
}
126+
127+
struct URLEntry {
128+
129+
var name: String
130+
var url: String
131+
var area: Rectangle
132+
var chatID: Int64
133+
var delay: Int = 0
134+
var captureElement: String = ""
135+
var clickElement: String = ""
136+
var waitElement: String = ""
137+
138+
}
139+
140+
struct Rectangle {
141+
var x: Int
142+
var y: Int
143+
var width: Int
144+
var height: Int
145+
146+
static let zero = Rectangle(x: 0, y: 0, width: 0, height: 0)
147+
}

0 commit comments

Comments
 (0)