using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using SleepeyDev.OnTogetherPomoNotify.Notify;
using SleepeyDev.OnTogetherPomoNotify.Pomodoro;
using SleepeyDev.OnTogetherPomoNotify.UI;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("SleepeyDev")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+65ccdec13cf2eefc4f6e6c5978198ed29bf47af6")]
[assembly: AssemblyProduct("OnTogetherPomoNotify")]
[assembly: AssemblyTitle("OnTogetherPomoNotify")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace SleepeyDev.OnTogetherPomoNotify
{
public static class ModConfig
{
public static ConfigEntry<bool> Enabled;
public static ConfigEntry<string> BotToken;
public static ConfigEntry<string> ChatId;
public static ConfigEntry<string> Prefix;
public static ConfigEntry<bool> NotifyPhaseChange;
public static ConfigEntry<bool> NotifyWarning;
public static ConfigEntry<string> WarningMinutes;
public static ConfigEntry<bool> NotifyStartStop;
public static ConfigEntry<bool> NotifySummary;
public static ConfigEntry<KeyCode> MenuKey;
public static ConfigEntry<float> UiScale;
public static ConfigEntry<float> WindowX;
public static ConfigEntry<float> WindowY;
public static ConfigEntry<bool> VerboseLogging;
public static void Bind(ConfigFile cfg)
{
//IL_014b: Unknown result type (might be due to invalid IL or missing references)
//IL_0155: Expected O, but got Unknown
Enabled = cfg.Bind<bool>("Telegram", "Enabled", true, "Master switch. Off means nothing is sent and no network call is made at all.");
BotToken = cfg.Bind<string>("Telegram", "BotToken", "", "Bot token from @BotFather, of the form 8123456789:AA...\nTREAT THIS AS A PASSWORD. Anyone who has it can read everything your bot receives\nand post as it. It is stored here in plain text because there is nowhere else to put\nit; do not paste this file, or a screenshot of it, into a chat or a bug report.\nThe mod never writes the token to the log.");
ChatId = cfg.Bind<string>("Telegram", "ChatId", "", "Where to send. Your own numeric id for a private chat, or -100... for a channel.\nMessage your bot once, then press 'Find chat id' in the mod's window and it fills\nthis in for you.");
Prefix = cfg.Bind<string>("Telegram", "Prefix", "", "Put in front of every message. Useful if one chat receives several sources.");
NotifyPhaseChange = cfg.Bind<bool>("Notify", "PhaseChange", true, "Focus ending and a break starting are the same moment, and get one message, not two.");
NotifyWarning = cfg.Bind<bool>("Notify", "Warning", true, "Send a heads-up shortly before the current phase ends.");
WarningMinutes = cfg.Bind<string>("Notify", "WarningMinutes", "5, 1", "Minutes-remaining marks that trigger a warning. Comma separated; 1 to 180.\nA mark is skipped when the timer jumps past it - syncing with another player moves\nthe clock, and 'five minutes left' arriving with forty seconds left helps nobody.");
NotifyStartStop = cfg.Bind<bool>("Notify", "StartStop", true, "Send when the timer is started, stopped, paused or resumed.");
NotifySummary = cfg.Bind<bool>("Notify", "Summary", true, "Append what the run has achieved to the long-break and stopped messages.");
MenuKey = cfg.Bind<KeyCode>("UI", "MenuKey", (KeyCode)282, "Opens the settings window. F3 is taken by Local Playlist, F2 by Pomodoro Sound,\nF4-F6 and F10-F11 by Day and Night, F7-F9 by FrameCare and BlueSage.");
UiScale = cfg.Bind<float>("UI", "UiScale", 0f, new ConfigDescription("Size of the settings window. 0 picks a size from your screen height, which is what\nyou want on anything above 1080p.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 4f), Array.Empty<object>()));
WindowX = cfg.Bind<float>("UI", "WindowX", 60f, "Remembered window position, in unscaled points. Moving the window writes these.");
WindowY = cfg.Bind<float>("UI", "WindowY", 60f, "See WindowX.");
VerboseLogging = cfg.Bind<bool>("Diagnostics", "VerboseLogging", false, "Log every snapshot decision and every send. Noisy; for bug reports.\nThe bot token is redacted out of anything logged, verbose or not.");
}
public static bool On(ConfigEntry<bool> entry)
{
return entry?.Value ?? false;
}
public static string Str(ConfigEntry<string> entry)
{
if (entry != null && entry.Value != null)
{
return entry.Value;
}
return "";
}
public static PlannerSettings Planner()
{
return new PlannerSettings
{
PhaseChange = On(NotifyPhaseChange),
Warning = On(NotifyWarning),
StartStop = On(NotifyStartStop),
WarningMinutes = PlannerSettings.ParseMinutes(Str(WarningMinutes))
};
}
}
public sealed class ModRoot : MonoBehaviour
{
public static ModRoot Instance { get; private set; }
public static void Create()
{
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Expected O, but got Unknown
if (!((Object)(object)Instance != (Object)null))
{
GameObject val = new GameObject("OTPN_ModRoot")
{
hideFlags = (HideFlags)61
};
Object.DontDestroyOnLoad((Object)val);
Instance = val.AddComponent<ModRoot>();
}
}
public static Coroutine Run(IEnumerator routine)
{
if ((Object)(object)Instance == (Object)null || routine == null)
{
return null;
}
return ((MonoBehaviour)Instance).StartCoroutine(routine);
}
private void Update()
{
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
if (ModConfig.MenuKey != null && Input.GetKeyDown(ModConfig.MenuKey.Value))
{
SettingsWindow.Toggle();
}
try
{
PhaseWatcher.Tick();
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("PhaseWatcher.Tick threw, notifications off: " + ex));
PhaseWatcher.Disable();
}
try
{
Telegram.Tick();
}
catch (Exception ex2)
{
Plugin.Log.LogError((object)("Telegram.Tick threw: " + ex2));
}
}
private void OnGUI()
{
try
{
SettingsWindow.Draw();
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Settings window failed, closing it: " + ex));
SettingsWindow.Close();
}
}
private void OnDestroy()
{
InputGate.Release();
}
private void OnApplicationQuit()
{
InputGate.Release();
}
}
[BepInPlugin("com.sleepeydev.ontogether.pomonotify", "On-Together PomoNotify", "1.0.0")]
public sealed class Plugin : BaseUnityPlugin
{
public const string Guid = "com.sleepeydev.ontogether.pomonotify";
public static ManualLogSource Log { get; private set; }
private void Awake()
{
//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
Log = ((BaseUnityPlugin)this).Logger;
try
{
ModConfig.Bind(((BaseUnityPlugin)this).Config);
}
catch (Exception ex)
{
Log.LogError((object)("Config binding failed - some features will switch themselves off: " + ex));
}
ModRoot.Create();
SceneManager.activeSceneChanged += OnSceneChanged;
Log.LogInfo((object)("PomoNotify " + ((BaseUnityPlugin)this).Info.Metadata.Version?.ToString() + " ready. Press " + ((ModConfig.MenuKey != null) ? ((object)ModConfig.MenuKey.Value/*cast due to .constrained prefix*/).ToString() : "F1") + " for its settings."));
Refl.LogHealth();
}
private void OnSceneChanged(Scene from, Scene to)
{
try
{
PhaseWatcher.Forget();
Telegram.Clear();
InputGate.Release();
}
catch (Exception ex)
{
Log.LogWarning((object)("Scene change handling: " + ex.Message));
}
}
private void OnDestroy()
{
SceneManager.activeSceneChanged -= OnSceneChanged;
}
}
public static class Refl
{
private static readonly List<string> Missing = new List<string>();
private static readonly MethodInfo GetInfo = Method(typeof(PomodoroController), "GetPomodoroInfo");
private static readonly PropertyInfo WritingProp = Property(typeof(TaskManager), "IsWriting");
private static readonly FieldInfo FType = Field(typeof(PomodoroInfo), "Type");
private static readonly FieldInfo FMinute = Field(typeof(PomodoroInfo), "CurrentMinute");
private static readonly FieldInfo FSecond = Field(typeof(PomodoroInfo), "CurrentSecond");
private static readonly FieldInfo FTotalSession = Field(typeof(PomodoroInfo), "TotalSession");
private static readonly FieldInfo FSession = Field(typeof(PomodoroInfo), "CurrentSession");
private static readonly FieldInfo FBreak = Field(typeof(PomodoroInfo), "BreakTime");
private static readonly FieldInfo FLongBreak = Field(typeof(PomodoroInfo), "LongBreakTime");
private static readonly FieldInfo FStudy = Field(typeof(PomodoroInfo), "StudyTime");
private static readonly FieldInfo FPaused = Field(typeof(PomodoroInfo), "IsPaused");
public static bool Ready
{
get
{
if (GetInfo != null && FType != null && FMinute != null && FSecond != null && FTotalSession != null && FSession != null && FBreak != null && FLongBreak != null && FStudy != null)
{
return FPaused != null;
}
return false;
}
}
public static bool CanGateInput
{
get
{
if (WritingProp != null)
{
return WritingProp.CanWrite;
}
return false;
}
}
public static PomodoroSnapshot Read()
{
PomodoroSnapshot result = default(PomodoroSnapshot);
if (!Ready)
{
return result;
}
PomodoroController i = MonoSingleton<PomodoroController>.I;
if ((Object)(object)i == (Object)null)
{
return result;
}
object obj = GetInfo.Invoke(i, null);
if (obj == null)
{
return result;
}
result.Present = true;
result.Phase = (Phase)(int)FType.GetValue(obj);
result.Minute = (int)FMinute.GetValue(obj);
result.Second = (float)FSecond.GetValue(obj);
result.SessionsPerLongBreak = (int)FTotalSession.GetValue(obj);
result.Session = (int)FSession.GetValue(obj);
result.BreakMinutes = (int)FBreak.GetValue(obj);
result.LongBreakMinutes = (int)FLongBreak.GetValue(obj);
result.StudyMinutes = (int)FStudy.GetValue(obj);
result.Paused = (bool)FPaused.GetValue(obj);
return result;
}
public static bool GetWriting()
{
try
{
TaskManager i = MonoSingleton<TaskManager>.I;
if ((Object)(object)i == (Object)null || WritingProp == null)
{
return false;
}
return (bool)WritingProp.GetValue(i, null);
}
catch (Exception)
{
return false;
}
}
public static void SetWriting(bool value)
{
try
{
TaskManager i = MonoSingleton<TaskManager>.I;
if (!((Object)(object)i == (Object)null) && !(WritingProp == null) && WritingProp.CanWrite)
{
WritingProp.SetValue(i, value, null);
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not set the game's typing flag: " + ex.Message));
}
}
private static MethodInfo Method(Type owner, string name)
{
MethodInfo methodInfo = AccessTools.Method(owner, name, (Type[])null, (Type[])null);
if (methodInfo == null)
{
Missing.Add(owner.Name + "." + name + "()");
}
return methodInfo;
}
private static PropertyInfo Property(Type owner, string name)
{
PropertyInfo propertyInfo = AccessTools.Property(owner, name);
if (propertyInfo == null)
{
Missing.Add(owner.Name + "." + name);
}
return propertyInfo;
}
private static FieldInfo Field(Type owner, string name)
{
FieldInfo fieldInfo = AccessTools.Field(owner, name);
if (fieldInfo == null)
{
Missing.Add(owner.Name + "." + name);
}
return fieldInfo;
}
public static void LogHealth()
{
if (Missing.Count == 0)
{
Plugin.Log.LogInfo((object)"Game handles: all resolved.");
return;
}
Plugin.Log.LogWarning((object)("Game handles missing (the game was probably updated): " + string.Join(", ", Missing.ToArray())));
Plugin.Log.LogWarning((object)("Feature availability - notifications: " + (Ready ? "on" : "OFF") + ", input blocking while the window is open: " + (CanGateInput ? "on" : "OFF")));
}
}
}
namespace SleepeyDev.OnTogetherPomoNotify.UI
{
public static class InputGate
{
private static bool _ours;
public static void Hold()
{
if (Refl.CanGateInput && !Refl.GetWriting())
{
Refl.SetWriting(value: true);
_ours = true;
}
}
public static void Release()
{
if (_ours)
{
_ours = false;
if (Refl.CanGateInput && Refl.GetWriting())
{
Refl.SetWriting(value: false);
}
}
}
}
public static class SettingsWindow
{
[CompilerGenerated]
private static class <>O
{
public static WindowFunction <0>__Body;
}
private const int WindowId = 1879;
private const float Width = 440f;
private const float LabelWidth = 74f;
private static Rect _rect = new Rect(60f, 60f, 440f, 120f);
private static bool _placed;
private static bool _showToken;
private static string _status = "";
private static string _token = "";
private static string _chat = "";
private static string _prefix = "";
private static string _warn = "";
public static bool Open { get; private set; }
private static bool Usable
{
get
{
if (ModConfig.Enabled != null && ModConfig.BotToken != null && ModConfig.ChatId != null && ModConfig.Prefix != null && ModConfig.WarningMinutes != null && ModConfig.NotifyPhaseChange != null && ModConfig.NotifyStartStop != null && ModConfig.NotifySummary != null && ModConfig.NotifyWarning != null && ModConfig.WindowX != null && ModConfig.WindowY != null)
{
return ModConfig.UiScale != null;
}
return false;
}
}
public static void Toggle()
{
if (Open)
{
Close();
}
else
{
Show();
}
}
public static void Show()
{
if (!Usable)
{
Plugin.Log.LogWarning((object)"The settings window needs the config, and binding it failed. Edit com.sleepeydev.ontogether.pomonotify.cfg by hand.");
return;
}
if (!_placed)
{
((Rect)(ref _rect)).x = ModConfig.WindowX.Value;
((Rect)(ref _rect)).y = ModConfig.WindowY.Value;
_placed = true;
}
_token = ModConfig.Str(ModConfig.BotToken);
_chat = ModConfig.Str(ModConfig.ChatId);
_prefix = ModConfig.Str(ModConfig.Prefix);
_warn = ModConfig.Str(ModConfig.WarningMinutes);
_showToken = false;
_status = "";
Open = true;
}
public static void Close()
{
if (Open)
{
Open = false;
Commit();
InputGate.Release();
}
}
private static void Commit()
{
Set(ModConfig.BotToken, _token.Trim());
Set(ModConfig.ChatId, _chat.Trim());
Set(ModConfig.Prefix, _prefix);
Set(ModConfig.WarningMinutes, _warn);
if (!Mathf.Approximately(ModConfig.WindowX.Value, ((Rect)(ref _rect)).x))
{
ModConfig.WindowX.Value = ((Rect)(ref _rect)).x;
}
if (!Mathf.Approximately(ModConfig.WindowY.Value, ((Rect)(ref _rect)).y))
{
ModConfig.WindowY.Value = ((Rect)(ref _rect)).y;
}
}
private static void Set(ConfigEntry<string> entry, string value)
{
if (entry != null && entry.Value != value)
{
entry.Value = value;
}
}
public static void Draw()
{
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
//IL_005f: Unknown result type (might be due to invalid IL or missing references)
//IL_0064: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Unknown result type (might be due to invalid IL or missing references)
//IL_0075: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Unknown result type (might be due to invalid IL or missing references)
//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
//IL_0141: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
//IL_00b3: Expected O, but got Unknown
if (Open && Usable)
{
InputGate.Hold();
float num = ((ModConfig.UiScale.Value > 0f) ? ModConfig.UiScale.Value : Mathf.Max(1f, Mathf.Round((float)Screen.height / 900f * 2f) / 2f));
Matrix4x4 matrix = GUI.matrix;
GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(num, num, 1f));
((Rect)(ref _rect)).width = 440f;
Rect rect = _rect;
object obj = <>O.<0>__Body;
if (obj == null)
{
WindowFunction val = Body;
<>O.<0>__Body = val;
obj = (object)val;
}
_rect = GUILayout.Window(1879, rect, (WindowFunction)obj, "Pomodoro → Telegram", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(440f) });
((Rect)(ref _rect)).x = Mathf.Clamp(((Rect)(ref _rect)).x, -300f, Mathf.Max(0f, (float)Screen.width / num - 90f));
((Rect)(ref _rect)).y = Mathf.Clamp(((Rect)(ref _rect)).y, 0f, Mathf.Max(0f, (float)Screen.height / num - 40f));
GUI.matrix = matrix;
}
}
private static void Body(int id)
{
//IL_03c0: Unknown result type (might be due to invalid IL or missing references)
GUILayout.Space(4f);
ModConfig.Enabled.Value = GUILayout.Toggle(ModConfig.Enabled.Value, " Send notifications", Array.Empty<GUILayoutOption>());
GUILayout.Space(8f);
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label("Bot token", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(74f) });
_token = (_showToken ? GUILayout.TextField(_token, 100, Array.Empty<GUILayoutOption>()) : GUILayout.PasswordField(_token, '*', 100, Array.Empty<GUILayoutOption>()));
_showToken = GUILayout.Toggle(_showToken, "Show", GUI.skin.button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(52f) });
GUILayout.EndHorizontal();
if (_token.Trim().Length > 0 && !Secret.LooksLikeToken(_token.Trim()))
{
Note("That does not look like a token. BotFather gives you 8123456789:AA…");
}
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label("Chat id", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(74f) });
_chat = GUILayout.TextField(_chat, 32, Array.Empty<GUILayoutOption>());
if (GUILayout.Button("Find", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(52f) }))
{
Commit();
_status = "Asking Telegram…";
Telegram.FindChatId(delegate(bool ok, string msg)
{
_status = msg;
if (ok)
{
_chat = ModConfig.Str(ModConfig.ChatId);
}
});
}
GUILayout.EndHorizontal();
Note("Write anything to your bot in Telegram, then press Find.");
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label("Prefix", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(74f) });
_prefix = GUILayout.TextField(_prefix, 40, Array.Empty<GUILayoutOption>());
GUILayout.EndHorizontal();
GUILayout.Space(10f);
GUILayout.Label("Send me", Array.Empty<GUILayoutOption>());
ModConfig.NotifyPhaseChange.Value = GUILayout.Toggle(ModConfig.NotifyPhaseChange.Value, " focus and break changes", Array.Empty<GUILayoutOption>());
ModConfig.NotifyStartStop.Value = GUILayout.Toggle(ModConfig.NotifyStartStop.Value, " start, stop, pause and resume", Array.Empty<GUILayoutOption>());
ModConfig.NotifySummary.Value = GUILayout.Toggle(ModConfig.NotifySummary.Value, " a summary at the long break and at the end", Array.Empty<GUILayoutOption>());
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
ModConfig.NotifyWarning.Value = GUILayout.Toggle(ModConfig.NotifyWarning.Value, " a warning at", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) });
_warn = GUILayout.TextField(_warn, 24, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) });
GUILayout.Label("minutes left", Array.Empty<GUILayoutOption>());
GUILayout.EndHorizontal();
if (ModConfig.NotifyWarning.Value && PlannerSettings.ParseMinutes(_warn).Length == 0)
{
Note("No usable numbers there, so no warnings would be sent.");
}
GUILayout.Space(10f);
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
if (GUILayout.Button("Send a test message", Array.Empty<GUILayoutOption>()))
{
Commit();
_status = "Sending…";
Telegram.SendNow("✅ On-Together is connected to this chat.", delegate(bool ok, string msg)
{
_status = msg;
});
}
if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }))
{
Close();
}
GUILayout.EndHorizontal();
GUILayout.Space(6f);
GUILayout.Label(string.IsNullOrEmpty(_status) ? Readiness() : _status, Array.Empty<GUILayoutOption>());
GUILayout.Label(TimerLine(), Array.Empty<GUILayoutOption>());
GUI.DragWindow(new Rect(0f, 0f, 10000f, 20f));
}
private static void Note(string text)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
Color color = GUI.color;
GUI.color = new Color(color.r, color.g, color.b, color.a * 0.6f);
GUILayout.Label(text, Array.Empty<GUILayoutOption>());
GUI.color = color;
}
private static string Readiness()
{
if (!Telegram.Configured(out var why))
{
return why;
}
return "Ready.";
}
private static string TimerLine()
{
PomodoroSnapshot current = PhaseWatcher.Current;
if (!current.Running)
{
return "Timer: not running.";
}
string text = ((current.Phase == Phase.Study) ? "focus" : ((current.Phase == Phase.Break) ? "break" : "long break"));
string text2 = "Timer: " + text + ", " + current.RemainingClock() + " left" + (current.Paused ? " (paused)" : "");
RunTally progress = PhaseWatcher.Progress;
if (progress.Any)
{
text2 = text2 + " · " + progress.FocusBlocks + " done, " + progress.FocusMinutes + " min";
}
return text2;
}
}
}
namespace SleepeyDev.OnTogetherPomoNotify.Pomodoro
{
public static class MessageText
{
public static string Render(NotifyEvent e, RunTally tally, bool includeSummary, string prefix)
{
StringBuilder stringBuilder = new StringBuilder();
if (!string.IsNullOrEmpty(prefix))
{
stringBuilder.Append(prefix.Trim()).Append(' ');
}
switch (e.Kind)
{
case NotifyKind.Started:
stringBuilder.Append("▶\ufe0f ").Append(Starting(e.To, e.ToMinutes));
break;
case NotifyKind.PhaseChanged:
stringBuilder.Append(Changed(e, tally));
break;
case NotifyKind.Warning:
stringBuilder.Append("⏳ ").Append(e.WarningMinutes).Append((e.WarningMinutes == 1) ? " minute left of " : " minutes left of ")
.Append(Of(e.To));
break;
case NotifyKind.Paused:
stringBuilder.Append("⏸\ufe0f Paused — ").Append(e.Clock).Append(" left of ")
.Append(Of(e.To));
break;
case NotifyKind.Resumed:
stringBuilder.Append("▶\ufe0f Resumed — ").Append(e.Clock).Append(" left of ")
.Append(Of(e.To));
break;
case NotifyKind.Stopped:
stringBuilder.Append("⏹\ufe0f Timer stopped.");
break;
}
bool flag = e.Kind == NotifyKind.Stopped || (e.Kind == NotifyKind.PhaseChanged && e.To == Phase.LongBreak);
if (includeSummary && flag && tally.Any)
{
stringBuilder.Append('\n').Append(Summary(tally));
}
return stringBuilder.ToString();
}
public static string Summary(RunTally t)
{
return "\ud83d\udcca " + t.FocusBlocks + ((t.FocusBlocks == 1) ? " focus block, " : " focus blocks, ") + t.FocusMinutes + " min focused";
}
private static string Changed(NotifyEvent e, RunTally tally)
{
return e.To switch
{
Phase.Break => "\ud83c\udf45 Focus done" + OfCycle(tally, e) + ". Break: " + e.ToMinutes + " min",
Phase.LongBreak => "\ud83c\udf45 Focus done. Long break: " + e.ToMinutes + " min",
Phase.Study => ((e.From == Phase.LongBreak) ? "\ud83c\udf34 Long break over — " : "☕ Break over — ") + "focus for " + e.ToMinutes + " min",
_ => "⏹\ufe0f Timer stopped.",
};
}
private static string OfCycle(RunTally tally, NotifyEvent e)
{
if (e.SessionsPerLongBreak <= 1 || tally.FocusBlocks <= 0)
{
return "";
}
return " — block " + tally.FocusBlocks + " of " + e.SessionsPerLongBreak;
}
private static string Starting(Phase p, int minutes)
{
return p switch
{
Phase.Study => "Focus started — " + minutes + " min",
Phase.Break => "Break started — " + minutes + " min",
Phase.LongBreak => "Long break started — " + minutes + " min",
_ => "Timer started",
};
}
private static string Of(Phase p)
{
return p switch
{
Phase.Study => "focus",
Phase.Break => "the break",
Phase.LongBreak => "the long break",
_ => "the timer",
};
}
}
public enum NotifyKind
{
Started,
PhaseChanged,
Warning,
Paused,
Resumed,
Stopped
}
public struct NotifyEvent
{
public NotifyKind Kind;
public Phase From;
public Phase To;
public int ToMinutes;
public int WarningMinutes;
public int Session;
public int SessionsPerLongBreak;
public string Clock;
}
public struct PlannerSettings
{
public bool PhaseChange;
public bool Warning;
public bool StartStop;
public int[] WarningMinutes;
public static PlannerSettings Everything(params int[] warnings)
{
return new PlannerSettings
{
PhaseChange = true,
Warning = true,
StartStop = true,
WarningMinutes = (warnings ?? new int[0])
};
}
public static int[] ParseMinutes(string csv)
{
List<int> list = new List<int>();
if (!string.IsNullOrEmpty(csv))
{
string[] array = csv.Split(',', ';', ' ');
for (int i = 0; i < array.Length; i++)
{
string text = array[i].Trim();
if (text.Length != 0 && int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result > 0 && result <= 180 && !list.Contains(result))
{
list.Add(result);
}
}
}
list.Sort();
list.Reverse();
return list.ToArray();
}
}
public static class NotificationPlanner
{
public const double StepSlackSeconds = 5.0;
public static List<NotifyEvent> Plan(PomodoroSnapshot prev, PomodoroSnapshot cur, PlannerSettings s, double elapsedSeconds)
{
List<NotifyEvent> list = new List<NotifyEvent>(2);
if (!prev.Present || !cur.Present)
{
return list;
}
if (!prev.Running && cur.Running)
{
if (s.StartStop)
{
list.Add(Start(cur));
}
return list;
}
if (prev.Running && !cur.Running)
{
if (s.StartStop)
{
list.Add(new NotifyEvent
{
Kind = NotifyKind.Stopped,
From = prev.Phase
});
}
return list;
}
if (!cur.Running)
{
return list;
}
if (prev.Phase != cur.Phase)
{
if (s.PhaseChange)
{
list.Add(Change(prev, cur));
}
return list;
}
if (prev.Paused != cur.Paused)
{
if (s.StartStop)
{
list.Add(new NotifyEvent
{
Kind = (cur.Paused ? NotifyKind.Paused : NotifyKind.Resumed),
To = cur.Phase,
Clock = cur.RemainingClock()
});
}
return list;
}
if (cur.Paused)
{
return list;
}
if (s.Warning)
{
int num = CrossedThreshold(prev, cur, s.WarningMinutes, elapsedSeconds);
if (num > 0)
{
list.Add(new NotifyEvent
{
Kind = NotifyKind.Warning,
To = cur.Phase,
WarningMinutes = num,
Clock = cur.RemainingClock()
});
}
}
return list;
}
private static NotifyEvent Start(PomodoroSnapshot cur)
{
return new NotifyEvent
{
Kind = NotifyKind.Started,
To = cur.Phase,
ToMinutes = cur.PhaseLengthMinutes,
Session = cur.Session,
SessionsPerLongBreak = cur.SessionsPerLongBreak,
Clock = cur.RemainingClock()
};
}
private static NotifyEvent Change(PomodoroSnapshot prev, PomodoroSnapshot cur)
{
return new NotifyEvent
{
Kind = NotifyKind.PhaseChanged,
From = prev.Phase,
To = cur.Phase,
ToMinutes = cur.PhaseLengthMinutes,
Session = cur.Session,
SessionsPerLongBreak = cur.SessionsPerLongBreak,
Clock = cur.RemainingClock()
};
}
private static int CrossedThreshold(PomodoroSnapshot prev, PomodoroSnapshot cur, int[] thresholds, double elapsedSeconds)
{
if (thresholds == null || thresholds.Length == 0)
{
return 0;
}
double remainingSeconds = prev.RemainingSeconds;
double remainingSeconds2 = cur.RemainingSeconds;
if (remainingSeconds2 >= remainingSeconds)
{
return 0;
}
double num = Math.Max(0.0, elapsedSeconds) * 2.0 + 5.0;
if (remainingSeconds - remainingSeconds2 > num)
{
return 0;
}
int num2 = 0;
foreach (int num3 in thresholds)
{
double num4 = (double)num3 * 60.0;
if (remainingSeconds > num4 && remainingSeconds2 <= num4 && (num2 == 0 || num3 < num2))
{
num2 = num3;
}
}
return num2;
}
}
public static class PhaseWatcher
{
private const float PollInterval = 1f;
private static bool _disabled;
private static bool _havePrev;
private static PomodoroSnapshot _prev;
private static double _prevAt;
private static RunTally _tally;
private static float _nextPoll;
public static PomodoroSnapshot Current => _prev;
public static RunTally Progress => _tally;
public static void Disable()
{
_disabled = true;
}
public static void Tick()
{
if (_disabled || !Refl.Ready || Time.unscaledTime < _nextPoll)
{
return;
}
_nextPoll = Time.unscaledTime + 1f;
PomodoroSnapshot pomodoroSnapshot = Refl.Read();
double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
if (!_havePrev || !_prev.Present || !pomodoroSnapshot.Present)
{
if (!pomodoroSnapshot.Present)
{
_tally = default(RunTally);
}
_prev = pomodoroSnapshot;
_prevAt = realtimeSinceStartupAsDouble;
_havePrev = true;
return;
}
double elapsedSeconds = realtimeSinceStartupAsDouble - _prevAt;
RunTally tally = _tally;
_tally = Tally.Advance(_tally, _prev, pomodoroSnapshot);
List<NotifyEvent> list;
try
{
list = NotificationPlanner.Plan(_prev, pomodoroSnapshot, ModConfig.Planner(), elapsedSeconds);
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Planning failed, notifications off: " + ex));
_disabled = true;
return;
}
_prev = pomodoroSnapshot;
_prevAt = realtimeSinceStartupAsDouble;
if (list.Count == 0)
{
return;
}
bool includeSummary = ModConfig.On(ModConfig.NotifySummary);
string prefix = ModConfig.Str(ModConfig.Prefix);
foreach (NotifyEvent item in list)
{
RunTally tally2 = ((item.Kind == NotifyKind.Stopped) ? tally : _tally);
string text = MessageText.Render(item, tally2, includeSummary, prefix);
if (ModConfig.On(ModConfig.VerboseLogging))
{
ManualLogSource log = Plugin.Log;
NotifyKind kind = item.Kind;
log.LogInfo((object)("Event " + kind.ToString() + " -> " + text.Replace("\n", " / ")));
}
Telegram.Queue(text);
}
}
public static void Forget()
{
_havePrev = false;
_prev = default(PomodoroSnapshot);
_tally = default(RunTally);
}
}
public enum Phase
{
None,
Study,
Break,
LongBreak
}
public struct PomodoroSnapshot
{
public bool Present;
public Phase Phase;
public bool Paused;
public int Minute;
public float Second;
public int Session;
public int SessionsPerLongBreak;
public int StudyMinutes;
public int BreakMinutes;
public int LongBreakMinutes;
public bool Running
{
get
{
if (Present)
{
return Phase != Phase.None;
}
return false;
}
}
public int PhaseLengthMinutes => Phase switch
{
Phase.Study => StudyMinutes,
Phase.Break => BreakMinutes,
Phase.LongBreak => LongBreakMinutes,
_ => 0,
};
public double ElapsedSeconds => (double)Minute * 60.0 + (double)Second;
public double RemainingSeconds
{
get
{
if (!Running)
{
return 0.0;
}
double num = (double)PhaseLengthMinutes * 60.0 - ElapsedSeconds;
if (!(num > 0.0))
{
return 0.0;
}
return num;
}
}
public string RemainingClock()
{
int num = (int)Math.Round(RemainingSeconds);
int num2 = num / 60;
int num3 = num % 60;
return ((num2 < 10) ? "0" : "") + num2 + ":" + ((num3 < 10) ? "0" : "") + num3;
}
}
public struct RunTally
{
public int FocusBlocks;
public int FocusMinutes;
public bool Any => FocusBlocks > 0;
}
public static class Tally
{
public static RunTally Advance(RunTally t, PomodoroSnapshot prev, PomodoroSnapshot cur)
{
if (!cur.Running)
{
return default(RunTally);
}
if (!prev.Present || !cur.Present)
{
return t;
}
if (!prev.Running)
{
return default(RunTally);
}
if (prev.Phase != Phase.Study || cur.Phase == Phase.Study)
{
return t;
}
if (prev.StudyMinutes <= 0 || prev.Minute + 1 < prev.StudyMinutes)
{
return t;
}
t.FocusBlocks++;
t.FocusMinutes += prev.StudyMinutes;
return t;
}
}
}
namespace SleepeyDev.OnTogetherPomoNotify.Notify
{
public static class Secret
{
public static bool LooksLikeToken(string token)
{
if (string.IsNullOrEmpty(token))
{
return false;
}
int num = token.IndexOf(':');
if (num < 3 || num > 16)
{
return false;
}
if (num + 20 > token.Length)
{
return false;
}
for (int i = 0; i < num; i++)
{
if (token[i] < '0' || token[i] > '9')
{
return false;
}
}
for (int j = num + 1; j < token.Length; j++)
{
char c = token[j];
if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '_' && c != '-')
{
return false;
}
}
return true;
}
public static string Mask(string token)
{
if (string.IsNullOrEmpty(token))
{
return "";
}
int num = token.IndexOf(':');
if (num <= 0)
{
return new string('*', Math.Min(token.Length, 12));
}
return token.Substring(0, num) + ":" + new string('*', 8);
}
public static string Redact(string text, string token)
{
if (string.IsNullOrEmpty(text))
{
return text;
}
if (!string.IsNullOrEmpty(token) && token.Length >= 8)
{
text = text.Replace(token, "<token>");
}
return RedactBotPath(text);
}
private static string RedactBotPath(string text)
{
int num = text.IndexOf("/bot", StringComparison.Ordinal);
if (num < 0)
{
return text;
}
StringBuilder stringBuilder = new StringBuilder(text.Length);
int num2 = 0;
while (num >= 0)
{
int num3 = num + "/bot".Length;
int i;
for (i = num3; i < text.Length && text[i] >= '0' && text[i] <= '9'; i++)
{
}
if (i > num3 && i < text.Length && text[i] == ':')
{
int j;
for (j = i + 1; j < text.Length && text[j] != '/' && text[j] != '?' && text[j] != ' ' && text[j] != '"'; j++)
{
}
stringBuilder.Append(text, num2, num - num2).Append("/bot<token>");
num2 = j;
}
num = text.IndexOf("/bot", num + "/bot".Length, StringComparison.Ordinal);
}
stringBuilder.Append(text, num2, text.Length - num2);
return stringBuilder.ToString();
}
}
public static class Telegram
{
private const string Api = "https://api.telegram.org/bot";
private const float MinIntervalSeconds = 1.2f;
private const int MaxQueued = 20;
private const int MaxAttempts = 3;
private const int TimeoutSeconds = 15;
private static readonly Queue<string> Pending = new Queue<string>();
private static bool _busy;
private static float _nextSendAt;
private static bool _warnedFull;
public static string Status { get; private set; } = "";
public static int Queued => Pending.Count;
public static void Queue(string text)
{
if (string.IsNullOrEmpty(text))
{
return;
}
if (!Configured(out var why))
{
Status = why;
Plugin.Log.LogWarning((object)("Not sending: " + why));
return;
}
if (Pending.Count >= 20)
{
Pending.Dequeue();
if (!_warnedFull)
{
_warnedFull = true;
Plugin.Log.LogWarning((object)"Telegram queue is full; dropping the oldest message. Something is wrong with the connection, not with the timer.");
}
}
Pending.Enqueue(text);
}
public static void Tick()
{
if (!_busy && Pending.Count != 0 && !(Time.unscaledTime < _nextSendAt) && !((Object)(object)ModRoot.Instance == (Object)null))
{
string text = Pending.Dequeue();
_busy = true;
ModRoot.Run(SendRoutine(text, null, ownsQueue: true));
}
}
public static void SendNow(string text, Action<bool, string> done)
{
if (!Configured(out var why))
{
Status = why;
done?.Invoke(arg1: false, why);
}
else if ((Object)(object)ModRoot.Instance == (Object)null)
{
done?.Invoke(arg1: false, "The mod is not running yet.");
}
else
{
ModRoot.Run(SendRoutine(text, done, ownsQueue: false));
}
}
public static void FindChatId(Action<bool, string> done)
{
string token = Token();
if (!Secret.LooksLikeToken(token))
{
done?.Invoke(arg1: false, "Enter the bot token first.");
}
else if ((Object)(object)ModRoot.Instance == (Object)null)
{
done?.Invoke(arg1: false, "The mod is not running yet.");
}
else
{
ModRoot.Run(FindChatIdRoutine(token, done));
}
}
private static IEnumerator SendRoutine(string text, Action<bool, string> done, bool ownsQueue)
{
string token = Token();
string chat = ModConfig.Str(ModConfig.ChatId).Trim();
string url = "https://api.telegram.org/bot" + token + "/sendMessage";
bool ok = false;
string message = "";
for (int attempt = 1; attempt <= 3; attempt++)
{
WWWForm val = new WWWForm();
val.AddField("chat_id", chat);
val.AddField("text", text, Encoding.UTF8);
val.AddField("disable_web_page_preview", "true");
UnityWebRequest req = UnityWebRequest.Post(url, val);
try
{
req.timeout = 15;
yield return req.SendWebRequest();
string body = "";
try
{
body = ((req.downloadHandler != null) ? req.downloadHandler.text : "");
}
catch (Exception)
{
}
if ((int)req.result == 1 && TelegramJson.IsOk(body))
{
ok = true;
message = "Sent.";
break;
}
message = Describe(req, body, token);
if (((int)req.result == 2 || req.responseCode >= 500 || req.responseCode == 429) && attempt != 3)
{
goto IL_01bf;
}
}
finally
{
((IDisposable)req)?.Dispose();
}
break;
IL_01bf:
yield return (object)new WaitForSecondsRealtime((float)attempt * 5f);
}
if (ownsQueue)
{
_busy = false;
}
_nextSendAt = Time.unscaledTime + 1.2f;
Status = (ok ? ("Sent " + Stamp()) : message);
if (!ok)
{
Plugin.Log.LogWarning((object)("Telegram send failed: " + message));
}
else if (ModConfig.On(ModConfig.VerboseLogging))
{
Plugin.Log.LogInfo((object)"Telegram: sent.");
}
done?.Invoke(ok, message);
}
private static IEnumerator FindChatIdRoutine(string token, Action<bool, string> done)
{
string text = "https://api.telegram.org/bot" + token + "/getUpdates?limit=10&timeout=0";
UnityWebRequest req = UnityWebRequest.Get(text);
try
{
req.timeout = 15;
yield return req.SendWebRequest();
string body = ((req.downloadHandler != null) ? req.downloadHandler.text : "");
if ((int)req.result != 1 || !TelegramJson.IsOk(body))
{
string arg = (Status = Describe(req, body, token));
done?.Invoke(arg1: false, arg);
yield break;
}
string text3 = TelegramJson.LastChatId(body);
if (string.IsNullOrEmpty(text3))
{
Status = "No messages yet. Write anything to your bot in Telegram, then press this again.";
done?.Invoke(arg1: false, "No messages yet. Write anything to your bot in Telegram, then press this again.");
yield break;
}
ModConfig.ChatId.Value = text3;
Status = "Chat id found: " + text3;
Plugin.Log.LogInfo((object)"Chat id resolved from getUpdates.");
done?.Invoke(arg1: true, text3);
}
finally
{
((IDisposable)req)?.Dispose();
}
}
private static string Token()
{
return ModConfig.Str(ModConfig.BotToken).Trim();
}
public static bool Configured(out string why)
{
if (!ModConfig.On(ModConfig.Enabled))
{
why = "Notifications are switched off.";
return false;
}
if (!Secret.LooksLikeToken(Token()))
{
why = "No bot token, or it does not look like one.";
return false;
}
if (ModConfig.Str(ModConfig.ChatId).Trim().Length == 0)
{
why = "No chat id yet.";
return false;
}
why = "";
return true;
}
private static string Describe(UnityWebRequest req, string body, string token)
{
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Invalid comparison between Unknown and I4
string text = TelegramJson.Description(body);
if (!string.IsNullOrEmpty(text))
{
return "Telegram says: " + Secret.Redact(text, token) + ((req.responseCode > 0) ? (" (" + req.responseCode + ")") : "");
}
if ((int)req.result == 2)
{
return "Could not reach Telegram: " + Secret.Redact(req.error ?? "no connection", token);
}
return "Telegram refused the message" + ((req.responseCode > 0) ? (" (" + req.responseCode + ")") : "") + (string.IsNullOrEmpty(req.error) ? "" : (": " + Secret.Redact(req.error, token)));
}
private static string Stamp()
{
return DateTime.Now.ToString("HH:mm:ss");
}
public static void Clear()
{
Pending.Clear();
_warnedFull = false;
}
}
public static class TelegramJson
{
public static string Description(string body)
{
return StringField(body, "\"description\"");
}
public static bool IsOk(string body)
{
if (string.IsNullOrEmpty(body))
{
return false;
}
int num = body.IndexOf("\"ok\"", StringComparison.Ordinal);
if (num < 0)
{
return false;
}
num = SkipToValue(body, num + 4);
if (num >= 0 && num + 4 <= body.Length)
{
return body.Substring(num, 4) == "true";
}
return false;
}
public static string LastChatId(string body)
{
if (string.IsNullOrEmpty(body))
{
return null;
}
string result = null;
int startIndex = 0;
while (true)
{
startIndex = body.IndexOf("\"chat\":", startIndex, StringComparison.Ordinal);
if (startIndex < 0)
{
break;
}
int num = SkipWhitespace(body, startIndex + "\"chat\":".Length);
if (num >= 0 && num < body.Length && body[num] == '{')
{
int num2 = body.IndexOf("\"id\"", num, StringComparison.Ordinal);
if (num2 >= 0)
{
string text = NumberField(body, num2);
if (text != null)
{
result = text;
}
}
}
startIndex += "\"chat\":".Length;
}
return result;
}
private static string StringField(string body, string key)
{
if (string.IsNullOrEmpty(body))
{
return null;
}
int num = body.IndexOf(key, StringComparison.Ordinal);
if (num < 0)
{
return null;
}
num = SkipToValue(body, num + key.Length);
if (num < 0 || num >= body.Length || body[num] != '"')
{
return null;
}
num++;
StringBuilder stringBuilder = new StringBuilder();
for (; num < body.Length && body[num] != '"'; num++)
{
if (body[num] == '\\' && num + 1 < body.Length)
{
num++;
}
stringBuilder.Append(body[num]);
}
return stringBuilder.ToString();
}
private static string NumberField(string body, int keyAt)
{
int i = SkipToValue(body, keyAt + 4);
if (i < 0)
{
return null;
}
int num = i;
if (i < body.Length && body[i] == '-')
{
i++;
}
for (; i < body.Length && body[i] >= '0' && body[i] <= '9'; i++)
{
}
if (i <= num || (i == num + 1 && body[num] == '-'))
{
return null;
}
return body.Substring(num, i - num);
}
private static int SkipToValue(string body, int at)
{
at = SkipWhitespace(body, at);
if (at < 0 || at >= body.Length || body[at] != ':')
{
return -1;
}
return SkipWhitespace(body, at + 1);
}
private static int SkipWhitespace(string body, int at)
{
while (at < body.Length && (body[at] == ' ' || body[at] == '\t' || body[at] == '\r' || body[at] == '\n'))
{
at++;
}
return at;
}
}
}